org.wso2.carbon.identity.authenticator.mutualssl.MutualSSLAuthenticator.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.identity.authenticator.mutualssl.MutualSSLAuthenticator.java

Source

/*
 * 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();
        }
    }

}