All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.enterprise.security.ssl.impl.SecuritySupportImpl Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
// Portions Copyright [2018-2022] [Payara Foundation and/or its affiliates]"
package com.sun.enterprise.security.ssl.impl;

import static java.lang.System.getProperty;
import static java.util.Arrays.asList;
import static java.util.Arrays.copyOf;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINEST;
import static java.util.logging.Level.WARNING;

import java.io.File;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Permission;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;

import org.glassfish.api.admin.ProcessEnvironment;
import org.glassfish.api.admin.ProcessEnvironment.ProcessType;
import org.glassfish.api.admin.ServerEnvironment;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.internal.api.Globals;
import org.glassfish.internal.embedded.Server;
import org.glassfish.logging.annotation.LogMessageInfo;
import org.glassfish.logging.annotation.LogMessagesResourceBundle;
import org.glassfish.logging.annotation.LoggerInfo;
import org.jvnet.hk2.annotations.Optional;
import org.jvnet.hk2.annotations.Service;

import com.sun.enterprise.security.ssl.manager.UnifiedX509KeyManager;
import com.sun.enterprise.security.ssl.manager.UnifiedX509TrustManager;
//V3:Commented import com.sun.enterprise.config.ConfigContext;
import com.sun.enterprise.server.pluggable.SecuritySupport;

/**
 * This implements SecuritySupport used in PluggableFeatureFactory.
 *
 * @author Shing Wai Chan
 */
// TODO: when we have two SecuritySupport implementations,
// we create Habitat we'll select which SecuritySupport implementation to use.
@Service
@Singleton
public class SecuritySupportImpl extends SecuritySupport {

    @LogMessagesResourceBundle
    public static final String SHARED_LOGMESSAGE_RESOURCE = "com.sun.enterprise.security.ssl.LogMessages";

    @LoggerInfo(subsystem = "SECURITY - SSL", description = "Security - SSL", publish = true)
    public static final String SEC_SSL_LOGGER = "javax.enterprise.system.security.ssl";

    protected static final Logger _logger = Logger.getLogger(SEC_SSL_LOGGER, SHARED_LOGMESSAGE_RESOURCE);

    @LogMessageInfo(message = "The SSL certificate with alias {0} has expired: {1}", level = "WARNING", cause = "Certificate expired.", action = "Check the expiration date of the certificate.")
    private static final String SSL_CERT_EXPIRED = "NCLS-SECURITY-05054";

    private static final String DEFAULT_KEYSTORE_PASS = "changeit";
    private static final String DEFAULT_TRUSTSTORE_PASS = "changeit";
    
    private static final Map> keyStores = new ConcurrentHashMap<>();
    private static final Map> trustStores = new ConcurrentHashMap<>();
    private static final Map> keyStorePasswords = new ConcurrentHashMap<>();
    private static final Map> tokenNames = new ConcurrentHashMap<>();
    
    private static final String DEFAULT_MAP_KEY = "key";

    private static boolean instantiated;
    private static boolean initialized;

    private Date initDate = new Date();

    @Inject
    private ServiceLocator serviceLocator;

    @Inject
    private ProcessEnvironment processEnvironment;

    @Inject
    @Optional
    private ServerEnvironment serverEnvironment;

    private MasterPasswordImpl masterPasswordHelper;

    public SecuritySupportImpl() {
        this(true);
    }

    protected SecuritySupportImpl(boolean init) {
        if (init) {
            initJKS();
        }
    }
   

    // --- implements SecuritySupport ---
    
    /**
     * This method returns an array of keystores containing keys and certificates.
     */
    @Override
    public KeyStore[] getKeyStores() {
        List keyStoresList = keyStores.get(DEFAULT_MAP_KEY);
        
        return keyStoresList.toArray(new KeyStore[keyStoresList.size()]);
    }
    
    /**
     * This method returns an array of truststores containing certificates.
     */
    @Override
    public KeyStore[] getTrustStores() {
        List trustStoresList = trustStores.get(DEFAULT_MAP_KEY);
        
        return trustStoresList.toArray(new KeyStore[trustStoresList.size()]);
    }
    
    /**
     * This method returns an array of token names in order corresponding to array of keystores.
     */
    @Override
    public String[] getTokenNames() {
        List tokenNamesList = tokenNames.get(DEFAULT_MAP_KEY);
        return tokenNamesList.toArray(new String[keyStores.get(DEFAULT_MAP_KEY).size()]);
    }
    
    /**
     * @param token
     * @return a keystore
     */
    @Override
    public KeyStore getKeyStore(String token) {
        int tokenIndex = getTokenIndex(token);
        if (tokenIndex < 0) {
            return null;
        }

        return keyStores.get(DEFAULT_MAP_KEY).get(tokenIndex);
    }

    /**
     * @param token
     * @return a truststore
     */
    @Override
    public KeyStore getTrustStore(String token) {
        int tokenIndex = getTokenIndex(token);
        if (tokenIndex < 0) {
            return null;
        }

        return trustStores.get(DEFAULT_MAP_KEY).get(tokenIndex);
    }
    
    @Override
    public void reset() {
        // Get store passwords from the master helper first
        char[] keyStorePass = masterPasswordHelper.getMasterPassword();
        char[] trustStorePass = keyStorePass;
        
        // Re-load stores (which looks at the system properties for the file names and potentially passwords)
        initStores(keyStorePass, trustStorePass);
    }
    
    @Override
    public KeyStore loadNullStore(String type, int index) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
        KeyStore keyStore = KeyStore.getInstance(type);
        keyStore.load(null, keyStorePasswords.get(DEFAULT_MAP_KEY).get(index));
        
        return keyStore;
    }

    @Override
    public KeyManager[] getKeyManagers(String algorithm) throws IOException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
        KeyStore[] keyStores = getKeyStores();

        ArrayList keyManagers = new ArrayList();
        for (int i = 0; i < keyStores.length; i++) {
            checkCertificateDates(keyStores[i]);
            
            KeyManager[] keyManagersPerStore = getKeyManagerFactory(keyStores[i], keyStorePasswords.get(DEFAULT_MAP_KEY).get(0), algorithm).getKeyManagers();
            if (keyManagersPerStore != null) {
                keyManagers.addAll(asList(keyManagersPerStore));
            }
        }

        KeyManager keyManager = new UnifiedX509KeyManager(
                keyManagers.toArray(new X509KeyManager[keyManagers.size()]),
                getTokenNames());

        return new KeyManager[] { keyManager };
    }

    @Override
    public TrustManager[] getTrustManagers(String algorithm) throws IOException, KeyStoreException, NoSuchAlgorithmException {
        
        ArrayList trustManagers = new ArrayList();
        for (KeyStore trustStore : getTrustStores()) {
            checkCertificateDates(trustStore);
            
            TrustManager[] trustManagersPerStore = getTrustManagerFactory(trustStore, algorithm).getTrustManagers();
            if (trustManagersPerStore != null) {
                trustManagers.addAll(asList(trustManagersPerStore));
            }
        }
        
        TrustManager trustManager;
        if (trustManagers.size() == 1) {
            trustManager = trustManagers.get(0);
        } else {
            trustManager = new UnifiedX509TrustManager(trustManagers.toArray(new X509TrustManager[trustManagers.size()]));
        }
        
        return new TrustManager[] { trustManager };
    }

    @Override
    public boolean verifyMasterPassword(char[] masterPass) {
        return Arrays.equals(masterPass, keyStorePasswords.get(DEFAULT_MAP_KEY).get(0));
    }

    @Override
    public void synchronizeKeyFile(Object configContext, String fileRealmName) throws Exception {
        // throw new UnsupportedOperationException("Not supported yet in V3.");
    }

    @Override
    public PrivateKey getPrivateKeyForAlias(String alias, int keystoreIndex) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
        checkPermission(KEYSTORE_PASS_PROP);

        Key key = keyStores
                    .get(DEFAULT_MAP_KEY)
                    .get(keystoreIndex)
                    .getKey(
                        alias, 
                        keyStorePasswords
                            .get(DEFAULT_MAP_KEY)
                            .get(keystoreIndex));
        
        if (key instanceof PrivateKey) {
            return (PrivateKey) key;
        }

        return null;
    }

    @Override
    public void checkPermission(String key) {
        try {
            // Checking a random permission to check if it is server.
            if (isEmbeddedServer() || serviceLocator == null || isACC() || isNotServerORACC()) {
                return;
            }

            AccessController.checkPermission(new RuntimePermission("SSLPassword"));
        } catch (AccessControlException e) {
            Permission permission = new PropertyPermission(key, "read");

            String message = e.getMessage();
            if (message != null) {
                message = message.replace(e.getPermission().toString(), permission.toString());
            }

            throw new AccessControlException(message, permission);
        }
    }
    
    /**
     * @return returned index
     */
    private int getTokenIndex(String token) {
        int tokenIndex = -1;
        if (token != null) {
            tokenIndex = tokenNames.get(DEFAULT_MAP_KEY).indexOf(token);
            if (tokenIndex < 0 && _logger.isLoggable(FINEST)) {
                _logger.log(FINEST, "token {0} is not found", token);
            }
        }

        return tokenIndex;
    }
    
    public boolean isACC() {
        return processEnvironment == null ? false : processEnvironment.getProcessType().equals(ProcessType.ACC);
    }

    public boolean isNotServerORACC() {
        return processEnvironment.getProcessType().equals(ProcessType.Other);
    }
    
    private void initJKS() {
        char[] keyStorePass = null;
        char[] trustStorePass = null;
        
        if (!isInstantiated()) {
            if (serviceLocator == null) {
                serviceLocator = Globals.getDefaultHabitat();
            }

            if (masterPasswordHelper == null && serviceLocator != null) {
                masterPasswordHelper = serviceLocator.getService(MasterPasswordImpl.class);
            }

            if (masterPasswordHelper != null) {
                keyStorePass = masterPasswordHelper.getMasterPassword();
                trustStorePass = keyStorePass;
            }
        }

        if (processEnvironment == null && serviceLocator != null) {
            processEnvironment = serviceLocator.getService(ProcessEnvironment.class);
        }

        if (serverEnvironment == null && serviceLocator != null) {
            serverEnvironment = serviceLocator.getService(ServerEnvironment.class);
        }

        if (!initialized) {
            initStores(keyStorePass, trustStorePass);
            initialized = true;
        }
    }
    
    /**
     * This method will get the keystore and trust store files and optionally the passwords from system properties, 
     * and then load both the keystore and truststore and add them into their corresponding list.
     * 
     * @param keyStorePassIn the password for the keystore, may be null, may be ignored.
     * @param trustStorePassIn the password for the truststore, may be null, may be ignored.
     */
    private void initStores(char[] keyStorePassIn, char[] trustStorePassIn) {
        String keyStoreFileName = System.getProperty(keyStoreProp);
        String additionalKeyStoreFileName = System.getProperty(additionalKeyStoreProp);
        String trustStoreFileName = System.getProperty(trustStoreProp);
        String additionalTrustStoreFileName = System.getProperty(additionalTrustStoreProp);
        
        // Initially consider the passwords that are passed-in
        char[] keyStorePass = keyStorePassIn;
        char[] trustStorePass = trustStorePassIn;
        
        // Under certain conditions, try get passwords from system properties instead
        if (shouldGetPassFromProperty(keyStorePass)) {
            keyStorePass = getKeyStorePass(keyStorePass);
            trustStorePass = getTrustStorePass(trustStorePass);
        }

        //Split additional stores by delimiter
        String[] additionalKeyStoreFileNames = null;
        String[] additionalTrustStoreFileNames = null;
        if(additionalKeyStoreFileName != null){
            additionalKeyStoreFileNames = additionalKeyStoreFileName.split(File.pathSeparator);
        }
        if(additionalTrustStoreFileName != null){
            additionalTrustStoreFileNames = additionalTrustStoreFileName.split(File.pathSeparator);
        }

        initStores(keyStoreFileName, keyStorePass, trustStoreFileName, trustStorePass, additionalKeyStoreFileNames, additionalTrustStoreFileNames);
    }
    
    /**
     * This method will load keystore and truststore and add into corresponding list.
     * 
     * @param keyStoreFileName
     * @param keyStorePass
     * @param trustStoreFileName
     * @param trustStorePass
     * @param additionalKeyStoreFileNames Array of additional keystores from JVM property: fish.payara.ssl.additionalKeyStores
     * @param additionalTrustStoreFileNames Array of additional truststores from JVM property: fish.payara.ssl.additionalTrustStores
     */
    private static void initStores(String keyStoreFileName, char[] keyStorePass, String trustStoreFileName, char[] trustStorePass, String[] additionalKeyStoreFileNames, String[] additionalTrustStoreFileNames) {
        try {
            
            // Create the initial lists to store the various data items
            
            List keyStoresList = new ArrayList();
            List trustStoresList = new ArrayList();
            List keyStorePasswordsList = new ArrayList();
            List tokenNamesList = new ArrayList();
            
            // Add the first item to each list 
            
            keyStoresList.add(loadStore(getProperty(KEYSTORE_TYPE_PROP, KeyStore.getDefaultType()), null, keyStoreFileName, keyStorePass));
            trustStoresList.add(loadStore(getProperty(TRUSTSTORE_TYPE_PROP, KeyStore.getDefaultType()), null, trustStoreFileName, trustStorePass));
            keyStorePasswordsList.add(copyOf(keyStorePass, keyStorePass.length));
            tokenNamesList.add(null); // This is slightly weird, but the original code did this too.

            // Load in all additional keystores
            try{
                if(additionalKeyStoreFileNames != null){
                    for(String keyStoreName : additionalKeyStoreFileNames){
                        keyStoresList.add(loadStore(getProperty(KEYSTORE_TYPE_PROP, KeyStore.getDefaultType()), null, keyStoreName, keyStorePass));
                    }
                } else {
                    _logger.fine("No additional keystores set");
                }

                if(additionalTrustStoreFileNames != null){
                    for(String trustStoreName : additionalTrustStoreFileNames){
                        trustStoresList.add(loadStore(getProperty(KEYSTORE_TYPE_PROP, KeyStore.getDefaultType()), null, trustStoreName, keyStorePass));
                    }
                } else {
                    _logger.fine("No additional truststores set");
                }
            }
            catch (FileNotFoundException fnfe){
                _logger.warning("Additional keystore or truststore file not found "+fnfe.getMessage());
            }

            
            // Atomically put each list in the concurrent maps holding that list
            // Note: this can either be the first insert for when this service is first initialized, or can
            // refresh existing ones.
            
            keyStores.put(DEFAULT_MAP_KEY, keyStoresList);
            trustStores.put(DEFAULT_MAP_KEY, trustStoresList);
            keyStorePasswords.put(DEFAULT_MAP_KEY, keyStorePasswordsList);
            tokenNames.put(DEFAULT_MAP_KEY, tokenNamesList);
            
        } catch (Exception ex) {
            _logger.severe("Failed to load key stores " + ex.getMessage());
            throw new IllegalStateException(ex);
        } finally {
            
            // Clear out the passwords (and ignore they already have been assigned many times to various strings)
            
            Arrays.fill(keyStorePass, ' ');
            Arrays.fill(trustStorePass, ' ');
        }
        
    }

    /**
     * This method will load keystore and truststore and add into corresponding list.
     *
     * @param tokenName
     * @param provider
     * @param keyStorePass
     * @param keyStoreFile
     * @param keyStoreType
     * @param trustStorePass
     * @param trustStoreFile
     * @param trustStoreType
     */
    protected synchronized static void loadStores(String tokenName, Provider provider, String keyStoreFile, char[] keyStorePass, String keyStoreType, String trustStoreFile, char[] trustStorePass, String trustStoreType) {
        try {
            keyStores.get(DEFAULT_MAP_KEY).add(loadStore(keyStoreType, provider, keyStoreFile, keyStorePass));
            trustStores.get(DEFAULT_MAP_KEY).add(loadStore(trustStoreType, provider, trustStoreFile, trustStorePass));
            keyStorePasswords.get(DEFAULT_MAP_KEY).add(Arrays.copyOf(keyStorePass, keyStorePass.length));
            tokenNames.get(DEFAULT_MAP_KEY).add(tokenName);
        } catch (Exception ex) {
            _logger.severe("Failed to load key stores " + ex.getMessage());
            throw new IllegalStateException(ex);
        }
    }

    
    /**
     * This method loads a single keystore with given keystore file and keystore password for a given keystore type and provider. It
     * always return a non-null keystore.
     *
     * @param keyStoreType
     * @param provider
     * @param keyStoreFile
     * @param keyStorePass
     *
     * @retun keystore loaded
     */
    private static KeyStore loadStore(String keyStoreType, Provider provider, String keyStoreFile, char[] keyStorePass) throws Exception {
        KeyStore keyStore = null;

        if (provider != null) {
            keyStore = KeyStore.getInstance(keyStoreType, provider);
        } else {
            keyStore = KeyStore.getInstance(keyStoreType);
        }

        if (keyStoreFile != null) {
            try (BufferedInputStream stream = new BufferedInputStream(new FileInputStream(keyStoreFile))) {

                if (_logger.isLoggable(FINE)) {
                    _logger.log(FINE, "Loading keystoreFile = {0}, keystorePass = {1}", new Object[] { keyStoreFile, keyStorePass });
                }

                keyStore.load(stream, keyStorePass);
            }
        } else {
            keyStore.load(null, keyStorePass);
        }

        return keyStore;
    }
    
    private boolean isEmbeddedServer() {
        return !Server.getServerNames().isEmpty();
    }
    
    /*
     * If we don't have a keystore password yet check the properties. Always do so for the app client case whether the
     * passwords have been found from master password helper or not.
     */
    private boolean shouldGetPassFromProperty(char[] keyStorePass) {
        return keyStorePass == null || isACC() || (serverEnvironment != null && serverEnvironment.isMicro());
    }
    
    private char[] getKeyStorePass(char[] keyStorePass) {
        String keyStorePassOverride = System.getProperty(KEYSTORE_PASS_PROP, DEFAULT_KEYSTORE_PASS);
        if (keyStorePassOverride == null) {
            return keyStorePass;
        }
        
        return keyStorePassOverride.toCharArray();
    }
    
    private char[] getTrustStorePass(char[] trustStorePass) {
        String trustStorePassOverride = System.getProperty(TRUSTSTORE_PASS_PROP, DEFAULT_TRUSTSTORE_PASS);
        if (trustStorePassOverride == null) {
            return trustStorePass;
        }
        
        return trustStorePassOverride.toCharArray();
    }

    private static synchronized boolean isInstantiated() {
        if (!instantiated) {
            instantiated = true;
            return false;
        }

        return true;
    }
    
    /*
     * Check X509 certificates in a store for expiration.
     */
    private void checkCertificateDates(KeyStore keyStore) throws KeyStoreException {
        Enumeration aliases = keyStore.aliases();
        
        while (aliases.hasMoreElements()) {
            String alias = aliases.nextElement();
            Certificate certificate = keyStore.getCertificate(alias);
            if (certificate instanceof X509Certificate) {
                if (((X509Certificate) certificate).getNotAfter().before(initDate)) {
                    _logger.log(WARNING, SSL_CERT_EXPIRED, new Object[] { alias, certificate});
                }
            }
        }
    }
    
    private TrustManagerFactory getTrustManagerFactory(KeyStore trustStore, String algorithm) throws NoSuchAlgorithmException, KeyStoreException {
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                algorithm != null ? algorithm : TrustManagerFactory.getDefaultAlgorithm());
        
        trustManagerFactory.init(trustStore);
        
        return trustManagerFactory;
    }
    
    private KeyManagerFactory getKeyManagerFactory(KeyStore keyStore, char[] keyStorePassword, String algorithm) throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException {
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
                algorithm != null ? algorithm : KeyManagerFactory.getDefaultAlgorithm());
        
        keyManagerFactory.init(keyStore, keyStorePassword);
        
        return keyManagerFactory;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy