org.wso2.carbon.identity.application.authentication.endpoint.util.MutualSSLManager.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.identity.application.authentication.endpoint.util.MutualSSLManager.java

Source

/*
 * Copyright (c) 2018, 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.authentication.endpoint.util;

import org.apache.axiom.om.util.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.securevault.SecretResolver;
import org.wso2.securevault.SecretResolverFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

public class MutualSSLManager {

    private static final Log log = LogFactory.getLog(MutualSSLManager.class);
    private static final String PROTECTED_TOKENS = "protectedTokens";
    private static final String DEFAULT_CALLBACK_HANDLER = "org.wso2.carbon.securevault.DefaultSecretCallbackHandler";
    private static final String SECRET_PROVIDER = "secretProvider";
    private static Properties prop;
    private static String carbonLogin = "";
    private static String usernameHeaderName = "";

    /**
     * Default keystore type of the client
     */
    private static final String keyStoreType = "JKS";
    /**
     * Default truststore type of the client
     */
    private static final String trustStoreType = "JKS";
    /**
     * Default keymanager type of the client
     */
    private static final String keyManagerType = "SunX509"; //Default Key Manager Type
    /**
     * Default trustmanager type of the client
     */
    private static final String trustManagerType = "SunX509"; //Default Trust Manager Type
    /**
     * Default ssl protocol for client
     */
    private static final String protocol = "TLSv1.2";
    private static KeyStore keyStore;
    private static KeyStore trustStore;
    private static char[] keyStorePassword;
    private static SSLSocketFactory sslSocketFactory;

    private MutualSSLManager() {

    }

    /**
     * Initialize Tenant data manager
     */
    public static synchronized void init() {

        try {
            prop = new Properties();
            String configFilePath = buildFilePath(Constants.TenantConstants.CONFIG_RELATIVE_PATH);
            File configFile = new File(configFilePath);

            if (configFile.exists()) {
                log.info(Constants.TenantConstants.CONFIG_FILE_NAME + " file loaded from "
                        + Constants.TenantConstants.CONFIG_RELATIVE_PATH);
                try (InputStream inputStream = new FileInputStream(configFile)) {

                    prop.load(inputStream);
                    if (isSecuredPropertyAvailable(prop)) {
                        // Resolve encrypted properties with secure vault
                        resolveSecrets(prop);
                    }
                }

            } else {
                try (InputStream inputStream = MutualSSLManager.class.getClassLoader()
                        .getResourceAsStream(Constants.TenantConstants.CONFIG_FILE_NAME)) {

                    if (inputStream != null) {
                        prop.load(inputStream);
                        log.debug(Constants.TenantConstants.CONFIG_FILE_NAME
                                + " file loaded from authentication endpoint webapp");
                    } else {
                        if (log.isDebugEnabled()) {
                            log.debug("Input stream is null while loading authentication endpoint from webapp");
                        }
                    }
                }
            }

            usernameHeaderName = getPropertyValue(Constants.TenantConstants.USERNAME_HEADER);
            carbonLogin = getPropertyValue(Constants.TenantConstants.USERNAME);

            // Base64 encoded username
            carbonLogin = Base64.encode(carbonLogin.getBytes(Constants.TenantConstants.CHARACTER_ENCODING));

            String clientKeyStorePath = buildFilePath(getPropertyValue(Constants.TenantConstants.CLIENT_KEY_STORE));
            String clientTrustStorePath = buildFilePath(
                    getPropertyValue(Constants.TenantConstants.CLIENT_TRUST_STORE));

            if (StringUtils.isNotEmpty(getPropertyValue(Constants.TenantConstants.TLS_PROTOCOL))) {
                TenantMgtAdminServiceClient.setProtocol(getPropertyValue(Constants.TenantConstants.TLS_PROTOCOL));
            }

            if (StringUtils.isNotEmpty(getPropertyValue(Constants.TenantConstants.KEY_MANAGER_TYPE))) {
                TenantMgtAdminServiceClient
                        .setKeyManagerType(getPropertyValue(Constants.TenantConstants.KEY_MANAGER_TYPE));
            }
            if (StringUtils.isNotEmpty(getPropertyValue(Constants.TenantConstants.TRUST_MANAGER_TYPE))) {
                TenantMgtAdminServiceClient
                        .setTrustManagerType(getPropertyValue(Constants.TenantConstants.TRUST_MANAGER_TYPE));
            }

            loadKeyStore(clientKeyStorePath, getPropertyValue(Constants.TenantConstants.CLIENT_KEY_STORE_PASSWORD));
            loadTrustStore(clientTrustStorePath,
                    getPropertyValue(Constants.TenantConstants.CLIENT_TRUST_STORE_PASSWORD));
            initMutualSSLConnection(Boolean
                    .parseBoolean(getPropertyValue(Constants.TenantConstants.HOSTNAME_VERIFICATION_ENABLED)));

        } catch (AuthenticationException | IOException e) {
            log.error("Initialization failed : ", e);
        }
    }

    /**
     * Build the absolute path of a give file path
     *
     * @param path File path
     * @return Absolute file path
     * @throws IOException
     */
    private static String buildFilePath(String path) throws IOException {

        if (StringUtils.isNotEmpty(path) && path.startsWith(Constants.TenantConstants.RELATIVE_PATH_START_CHAR)) {
            // Relative file path is given
            File currentDirectory = new File(
                    new File(Constants.TenantConstants.RELATIVE_PATH_START_CHAR).getAbsolutePath());
            path = currentDirectory.getCanonicalPath() + File.separator + path;
        }

        if (log.isDebugEnabled()) {
            log.debug("File path for KeyStore/TrustStore : " + path);
        }
        return path;
    }

    /**
     * Get property value by key
     *
     * @param key Property key
     * @return Property value
     */
    protected static String getPropertyValue(String key) {

        if ((Constants.SERVICES_URL.equals(key)) && !prop.containsKey(Constants.SERVICES_URL)) {
            String serviceUrl = IdentityUtil.getServicePath();
            return IdentityUtil.getServerURL(serviceUrl, true, true);
        }
        return prop.getProperty(key);
    }

    /**
     * There can be sensitive information like passwords in configuration file. If they are encrypted using secure
     * vault, this method will resolve them and replace with original values.
     */
    private static void resolveSecrets(Properties properties) {

        String protectedTokens = (String) properties.get(PROTECTED_TOKENS);

        if (StringUtils.isNotBlank(protectedTokens)) {
            String secretProvider = (String) properties.get(SECRET_PROVIDER);
            SecretResolver secretResolver;

            if (StringUtils.isBlank(secretProvider)) {
                properties.put(SECRET_PROVIDER, DEFAULT_CALLBACK_HANDLER);
            }

            secretResolver = SecretResolverFactory.create(properties, "");
            StringTokenizer st = new StringTokenizer(protectedTokens, ",");

            while (st.hasMoreElements()) {
                String element = st.nextElement().toString().trim();

                if (secretResolver.isTokenProtected(element)) {
                    if (log.isDebugEnabled()) {
                        log.debug("Resolving and replacing secret for " + element);
                    }
                    // Replaces the original encrypted property with resolved property
                    properties.put(element, secretResolver.resolve(element));
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("No encryption done for value with key :" + element);
                    }
                }
            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug("Secure vault encryption ignored since no protected tokens available");
            }
        }
    }

    /**
     * Get status of the availability of secured (with secure vault) properties
     *
     * @return availability of secured properties
     */
    private static boolean isSecuredPropertyAvailable(Properties properties) {

        Enumeration propertyNames = properties.propertyNames();

        while (propertyNames.hasMoreElements()) {
            String key = (String) propertyNames.nextElement();
            if (PROTECTED_TOKENS.equals(key) && StringUtils.isNotBlank(properties.getProperty(key))) {
                return true;
            }
        }
        return false;
    }

    /**
     * Load key store with given keystore.jks
     *
     * @param keyStorePath     Path to keystore
     * @param keyStorePassword Password of keystore
     * @throws AuthenticationException
     */
    public static void loadKeyStore(String keyStorePath, String keyStorePassword) throws AuthenticationException {

        try {
            MutualSSLManager.keyStorePassword = keyStorePassword.toCharArray();
            keyStore = KeyStore.getInstance(keyStoreType);
            try (InputStream fis = new FileInputStream(keyStorePath)) {
                keyStore.load(fis, MutualSSLManager.keyStorePassword);
            }
        } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) {
            throw new AuthenticationException("Error while trying to load Key Store.", e);
        }
    }

    /**
     * Load trust store with given .jks file
     *
     * @param trustStorePath     Path to truststore
     * @param trustStorePassword Password of truststore
     * @throws AuthenticationException
     */
    public static void loadTrustStore(String trustStorePath, String trustStorePassword)
            throws AuthenticationException {

        try {
            trustStore = KeyStore.getInstance(trustStoreType);
            try (InputStream is = new FileInputStream(trustStorePath)) {
                trustStore.load(is, trustStorePassword.toCharArray());
            }
        } catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
            throw new AuthenticationException("Error while trying to load Trust Store.", e);
        }
    }

    /**
     * Create basic SSL connection factory
     *
     * @throws AuthenticationException
     */
    public static void initMutualSSLConnection(boolean hostNameVerificationEnabled) throws AuthenticationException {

        try {
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keyManagerType);
            keyManagerFactory.init(keyStore, keyStorePassword);
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(trustManagerType);
            trustManagerFactory.init(trustStore);

            // Create and initialize SSLContext for HTTPS communication
            SSLContext sslContext = SSLContext.getInstance(protocol);

            if (hostNameVerificationEnabled) {
                sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
                sslSocketFactory = sslContext.getSocketFactory();

                if (log.isDebugEnabled()) {
                    log.debug("Mutual SSL Client initialized with Hostname Verification enabled");
                }
            } else {
                // All the code below is to overcome host name verification failure we get in certificate
                // validation due to self signed certificate.

                // Create empty HostnameVerifier
                HostnameVerifier hv = new HostnameVerifier() {
                    @Override
                    public boolean verify(String urlHostName, SSLSession session) {

                        return true;
                    }
                };

                // Create a trust manager that does not validate certificate chains
                TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {

                        return new java.security.cert.X509Certificate[0];
                    }

                    @Override
                    public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                        /*
                             skipped implementation
                        */
                    }

                    @Override
                    public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                        /*
                             skipped implementation
                         */
                    }
                } };

                sslContext.init(keyManagerFactory.getKeyManagers(), trustAllCerts,
                        new java.security.SecureRandom());

                if (log.isDebugEnabled()) {
                    log.debug("SSL Context is initialized with trust manager for excluding certificate validation");
                }
                SSLContext.setDefault(sslContext);
                sslSocketFactory = sslContext.getSocketFactory();
                HttpsURLConnection.setDefaultHostnameVerifier(hv);

                if (log.isDebugEnabled()) {
                    log.debug("Mutual SSL Client initialized with Hostname Verification disabled");
                }
            }
        } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException
                | KeyManagementException e) {
            throw new AuthenticationException("Error while trying to load Trust Store.", e);
        }
    }

    public static SSLSocketFactory getSslSocketFactory() {

        return sslSocketFactory;
    }

    public static String getCarbonLogin() {

        return carbonLogin;
    }

    public static String getUsernameHeaderName() {

        return usernameHeaderName;
    }
}