/* * Copyright (c) 2014, 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.authenticator.mutualssl; import org.apache.axiom.om.util.Base64; import org.apache.axiom.soap.SOAPEnvelope; import org.apache.axiom.soap.SOAPHeader; import org.apache.axiom.soap.SOAPHeaderBlock; import org.apache.axis2.context.MessageContext; import org.apache.axis2.transport.http.HTTPConstants; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; import org.wso2.carbon.core.security.AuthenticatorsConfiguration; import org.wso2.carbon.core.services.authentication.CarbonServerAuthenticator; import org.wso2.carbon.core.services.util.CarbonAuthenticationUtil; import org.wso2.carbon.identity.authenticator.mutualssl.internal.MutualSSLAuthenticatorServiceComponent; import org.wso2.carbon.user.api.TenantManager; import org.wso2.carbon.user.api.UserStoreManager; import org.wso2.carbon.utils.AuthenticationObserver; import org.wso2.carbon.utils.multitenancy.MultitenantUtils; import javax.servlet.http.HttpServletRequest; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.List; import java.util.Map; /** * Authenticator for certificate based two-way authentication */ public class MutualSSLAuthenticator implements CarbonServerAuthenticator { private static final int DEFAULT_PRIORITY_LEVEL = 5; private static final String AUTHENTICATOR_NAME = "MutualSSLAuthenticator"; private static final String MUTUAL_SSL_URL = "http://mutualssl.carbon.wso2.org"; /** * Header name of the username for mutual ssl authentication */ private static final String USERNAME_HEADER = "UsernameHeader"; /** * Configuration parameter name for trusted certificates list */ private static final String WHITE_LIST = "WhiteList"; /** * Configuration parameter name for enabling and disabling the trusted certificates list */ private static final String WHITE_LIST_ENABLED = "WhiteListEnabled"; /** * Attribute name for reading client certificate in the request */ private static final String JAVAX_SERVLET_REQUEST_CERTIFICATE = "javax.servlet.request.X509Certificate"; /** * Character encoding for Base64 to String conversions */ private static final String CHARACTER_ENCODING = "UTF-8"; /** * Logger for the class */ private static final Log log = LogFactory.getLog(MutualSSLAuthenticator.class); private static String usernameHeaderName = "UserName"; private static String[] whiteList; private static boolean whiteListEnabled = false; private static boolean authenticatorInitialized = false; /** * Initialize Mutual SSL Authenticator Configuration */ private synchronized static void init() { AuthenticatorsConfiguration authenticatorsConfiguration = AuthenticatorsConfiguration.getInstance(); // Read configuration for mutual ssl authenticator AuthenticatorsConfiguration.AuthenticatorConfig authenticatorConfig = authenticatorsConfiguration.getAuthenticatorConfig(AUTHENTICATOR_NAME); if (authenticatorConfig != null) { Map<String, String> configParameters = authenticatorConfig.getParameters(); if (configParameters != null) { if (configParameters.containsKey(USERNAME_HEADER)) { usernameHeaderName = configParameters.get(USERNAME_HEADER); } if (configParameters.containsKey(WHITE_LIST_ENABLED)) { whiteListEnabled = Boolean.parseBoolean(configParameters.get(WHITE_LIST_ENABLED)); if (log.isDebugEnabled()) { log.debug("Enabling trusted client certificates list : " + whiteListEnabled); } } if (whiteListEnabled) { // List of trusted thumbprints for clients is enabled if (configParameters.containsKey(WHITE_LIST)) { whiteList = configParameters.get(WHITE_LIST).trim().split(","); int index = 0; // Remove whitespaces in the thumbprints of white list for (String thumbprint : whiteList) { thumbprint = thumbprint.trim(); whiteList[index] = thumbprint; if (log.isDebugEnabled()) { log.debug("Client thumbprint " + thumbprint + " added to the white list"); } index++; } } else { log.error("Trusted client certificates list is enabled but empty"); return; } } authenticatorInitialized = true; } } else { if (log.isDebugEnabled()) { log.debug(AUTHENTICATOR_NAME + " configuration is not set for initialization"); } } } @Override public int getPriority() { AuthenticatorsConfiguration authenticatorsConfiguration = AuthenticatorsConfiguration.getInstance(); AuthenticatorsConfiguration.AuthenticatorConfig authenticatorConfig = authenticatorsConfiguration.getAuthenticatorConfig(AUTHENTICATOR_NAME); if (authenticatorConfig != null && authenticatorConfig.getPriority() > 0) { return authenticatorConfig.getPriority(); } return DEFAULT_PRIORITY_LEVEL; } @Override public boolean isDisabled() { AuthenticatorsConfiguration authenticatorsConfiguration = AuthenticatorsConfiguration.getInstance(); AuthenticatorsConfiguration.AuthenticatorConfig authenticatorConfig = authenticatorsConfiguration.getAuthenticatorConfig(AUTHENTICATOR_NAME); if (authenticatorConfig != null) { return authenticatorConfig.isDisabled(); } return false; } @Override public boolean authenticateWithRememberMe(MessageContext msgCxt) { return false; } @Override public String getAuthenticatorName() { return AUTHENTICATOR_NAME; } @Override public boolean isAuthenticated(MessageContext msgCxt) { boolean isAuthenticated = false; HttpServletRequest request = (HttpServletRequest) msgCxt.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST); Object certObject = request.getAttribute(JAVAX_SERVLET_REQUEST_CERTIFICATE); try { if (certObject != null) { if (!authenticatorInitialized) { init(); } if (!authenticatorInitialized) { log.error(AUTHENTICATOR_NAME + " failed initialization"); return false; } // <m:UserName xmlns:m="http://mutualssl.carbon.wso2.org" // soapenv:mustUnderstand="0">234</m:UserName> boolean trustedThumbprint = false; String thumbprint = null; if (certObject instanceof X509Certificate[]) { X509Certificate[] cert = (X509Certificate[]) certObject; if (whiteListEnabled && whiteList != null) { // Client certificate is always in the index 0 thumbprint = getThumbPrint(cert[0]); if (log.isDebugEnabled()) { log.debug("Client certificate thumbprint is " + thumbprint); } for (String whiteThumbprint : whiteList) { if (thumbprint.equals(whiteThumbprint)) { // Thumbprint of the client certificate is in the trusted list trustedThumbprint = true; if (log.isDebugEnabled()) { log.debug("Client certificate thumbprint matched with the white list"); } break; } } } } if (!whiteListEnabled || trustedThumbprint) { // WhiteList is disabled or client certificate is in the trusted list String userName = null; String usernameInHeader = request.getHeader(usernameHeaderName); boolean validHeader = false; if (StringUtils.isNotEmpty(usernameInHeader)) { //username is received in HTTP header encoded in base64 byte[] base64DecodedByteArray = Base64.decode(usernameInHeader); userName = new String(base64DecodedByteArray, CHARACTER_ENCODING); validHeader = true; if (log.isDebugEnabled()) { log.debug("Username for Mutual SSL : " + userName); } } if (StringUtils.isEmpty(userName)) { // Username is not received in HTTP Header. Check for SOAP header SOAPEnvelope envelope = msgCxt.getEnvelope(); SOAPHeader header = envelope.getHeader(); if (header != null) { List<SOAPHeaderBlock> headers = header.getHeaderBlocksWithNSURI(MUTUAL_SSL_URL); if (headers != null) { for (SOAPHeaderBlock soapHeaderBlock : headers) { if (usernameHeaderName.equals(soapHeaderBlock.getLocalName())) { // Username is received in SOAP header userName = soapHeaderBlock.getText(); validHeader = true; break; } } } } } if (!validHeader && log.isDebugEnabled()) { log.debug("'" + usernameHeaderName + "'" + " header is not received in HTTP or SOAP header"); } if (StringUtils.isNotEmpty(userName)) { String tenantDomain = MultitenantUtils.getTenantDomain(userName); userName = MultitenantUtils.getTenantAwareUsername(userName); TenantManager tenantManager = MutualSSLAuthenticatorServiceComponent.getRealmService().getTenantManager(); int tenantId = tenantManager.getTenantId(tenantDomain); handleAuthenticationStarted(tenantId); UserStoreManager userstore = MutualSSLAuthenticatorServiceComponent.getRealmService().getTenantUserRealm(tenantId) .getUserStoreManager(); if (userstore.isExistingUser(userName)) { // Username used for mutual ssl authentication is a valid user isAuthenticated = true; } if (isAuthenticated) { CarbonAuthenticationUtil.onSuccessAdminLogin(request.getSession(), userName, tenantId, tenantDomain, "Mutual SSL Authentication"); handleAuthenticationCompleted(tenantId, true); isAuthenticated = true; } else { if (log.isDebugEnabled()) { log.debug("Authentication rquest is rejected. User " + userName + " does not exist in userstore"); } CarbonAuthenticationUtil.onFailedAdminLogin(request.getSession(), userName, tenantId, "Mutual SSL Authentication", "User does not exist in userstore"); handleAuthenticationCompleted(tenantId, false); isAuthenticated = false; } } } else { if (log.isDebugEnabled()) { log.debug("Client Thumbprint " + thumbprint + " is not in the White List of " + AUTHENTICATOR_NAME); } } } else { throw new IllegalStateException("The certificate cannot be empty"); } } catch (Exception e) { log.error("Error authenticating the user " + e.getMessage(), e); } return isAuthenticated; } @Override public boolean isHandle(MessageContext msgCxt) { boolean canHandle = false; if (!isDisabled()) { if (!authenticatorInitialized) { init(); if (!authenticatorInitialized) { return canHandle; } } HttpServletRequest request = (HttpServletRequest) msgCxt.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST); String authorizationHeader = request.getHeader(HTTPConstants.HEADER_AUTHORIZATION); // This authenticator should kickin only if authorization headers are null if (authorizationHeader == null) { Object certObject = request.getAttribute(JAVAX_SERVLET_REQUEST_CERTIFICATE); if (certObject != null) { SOAPEnvelope envelope = msgCxt.getEnvelope(); SOAPHeader header = envelope.getHeader(); boolean validHeader = false; if (header != null) { List<SOAPHeaderBlock> headers = header.getHeaderBlocksWithNSURI(MUTUAL_SSL_URL); if (headers != null) { for (SOAPHeaderBlock soapHeaderBlock : headers) { if (usernameHeaderName.equals(soapHeaderBlock.getLocalName())) { //Username can be in SOAP Header canHandle = true; validHeader = true; break; } } } } if (!canHandle && StringUtils.isNotEmpty(request.getHeader(usernameHeaderName))) { validHeader = true; // Username is received in HTTP Header canHandle = true; } if (!validHeader && log.isDebugEnabled()) { log.debug("'" + usernameHeaderName + "'" + " header is not received in HTTP or SOAP header"); } } else { if (log.isDebugEnabled()) { log.debug("Server is not picking up the client certificate. Mutual SSL authentication is not" + "done"); } } } } else { if (log.isDebugEnabled()) { log.debug("MutualSSLAuthenticator is Disabled."); } } return canHandle; } /** * Helper method to retrieve the thumbprint of a X509 certificate * * @param cert X509 certificate * @return Thumbprint of the X509 certificate * @throws NoSuchAlgorithmException * @throws CertificateEncodingException */ private String getThumbPrint(X509Certificate cert) throws NoSuchAlgorithmException, CertificateEncodingException { MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] certEncoded = cert.getEncoded(); md.update(certEncoded); return hexify(md.digest()); } /** * Helper method to hexify a byte array. * * @param bytes Bytes of message digest * @return Hexadecimal representation */ private String hexify(byte bytes[]) { StringBuilder builder = new StringBuilder(bytes.length * 2); char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; for (byte byteValue : bytes) { builder.append(hexDigits[(byteValue & 0xf0) >> 4]).append(hexDigits[byteValue & 0x0f]); } return builder.toString(); } private void handleAuthenticationStarted(int tenantId) { BundleContext bundleContext = MutualSSLAuthenticatorServiceComponent.getBundleContext(); if (bundleContext != null) { ServiceTracker tracker = new ServiceTracker(bundleContext, AuthenticationObserver.class.getName(), null); tracker.open(); Object[] services = tracker.getServices(); if (services != null) { for (Object service : services) { ((AuthenticationObserver) service).startedAuthentication(tenantId); } } tracker.close(); } } private void handleAuthenticationCompleted(int tenantId, boolean isSuccessful) { BundleContext bundleContext = MutualSSLAuthenticatorServiceComponent.getBundleContext(); if (bundleContext != null) { ServiceTracker tracker = new ServiceTracker(bundleContext, AuthenticationObserver.class.getName(), null); tracker.open(); Object[] services = tracker.getServices(); if (services != null) { for (Object service : services) { ((AuthenticationObserver) service).completedAuthentication( tenantId, isSuccessful); } } tracker.close(); } } }