org.hyperic.util.security.KeystoreManager.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperic.util.security.KeystoreManager.java

Source

/**
 * NOTE: This copyright does *not* cover user programs that use Hyperic
 * program services by normal system calls through the application
 * program interfaces provided as part of the Hyperic Plug-in Development
 * Kit or the Hyperic Client Development Kit - this is merely considered
 * normal use of the program, and does *not* fall under the heading of
 *  "derived work".
 *
 *  Copyright (C) [2010], VMware, Inc.
 *  This file is part of Hyperic.
 *
 *  Hyperic is free software; you can redistribute it and/or modify
 *  it under the terms version 2 of the GNU General Public License as
 *  published by the Free Software Foundation. This program is distributed
 *  in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 *  PARTICULAR PURPOSE. See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 *  USA.
 *
 */
package org.hyperic.util.security;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.X509Certificate;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.net.ssl.X509TrustManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.conn.ssl.AbstractVerifier;
import org.hyperic.util.exec.Execute;
import org.hyperic.util.exec.ExecuteWatchdog;
import org.hyperic.util.exec.PumpStreamHandler;
import org.hyperic.util.file.FileUtil;
import org.hyperic.util.timer.StopWatch;
import org.springframework.util.StringUtils;

public class KeystoreManager {
    private final Log log;
    private final AtomicBoolean isDB = new AtomicBoolean(false);
    private static KeystoreManager keystoreManager = new KeystoreManager();

    private KeystoreManager() {
        this.log = LogFactory.getLog(KeystoreManager.class);
    }

    public static KeystoreManager getKeystoreManager() {
        return keystoreManager;
    }

    private String getDName(KeystoreConfig keystoreConfig) {
        return "CN=" + keystoreConfig.getKeyCN()
                + " (HQ Self-Signed Cert), OU=HQ, O=hyperic.net, L=Unknown, ST=Unknown, C=US";
    }

    public KeyStore getKeyStore(KeystoreConfig keystoreConfig) throws KeyStoreException, IOException {
        FileInputStream keyStoreFileInputStream = null;

        String filePath = keystoreConfig.getFilePath();
        String filePassword = keystoreConfig.getFilePassword();

        //check if keystoreConfig valid (block if it's null or "")
        String errorMsg = "";
        if (keystoreConfig.getAlias() == null) {
            errorMsg += " alias is null. ";
        }
        if (keystoreConfig.getFilePath() == null) {
            errorMsg += " filePath is null. ";
        }
        if (keystoreConfig.getFilePassword() == null) {
            errorMsg += " password is null. ";
        }
        if (!"".equals(errorMsg)) {
            throw new KeyStoreException(errorMsg);
        }

        try {
            KeyStore keystore = DbKeyStore.getInstance(KeyStore.getDefaultType(), isDB);
            File file = new File(filePath);
            char[] password = null;

            if (!file.exists()) {
                // ...if file doesn't exist, and path was user specified throw IOException...
                if (StringUtils.hasText(filePath) && !keystoreConfig.isHqDefault()) {
                    throw new IOException("User specified keystore [" + filePath + "] does not exist.");
                }

                password = filePassword.toCharArray();
                createInternalKeystore(keystoreConfig);
                FileUtil.setReadWriteOnlyByOwner(file);
            }

            // ...keystore exist, so init the file input stream...
            keyStoreFileInputStream = new FileInputStream(file);

            keystore.load(keyStoreFileInputStream, password);

            return keystore;
        } catch (NoSuchAlgorithmException e) {
            // can't check integrity of keystore, if this happens we're kind of screwed
            // is there anything we can do to self heal this problem?
            errorMsg = "The algorithm used to check the integrity of the keystore cannot be found.";
            throw new KeyStoreException(errorMsg, e);
        } catch (CertificateException e) {
            // there are some corrupted certificates in the keystore, a bad thing
            // is there anything we can do to self heal this problem?
            errorMsg = "Keystore cannot be loaded. One possibility is that the password is incorrect.";
            throw new KeyStoreException(errorMsg, e);
        } finally {
            if (keyStoreFileInputStream != null) {
                keyStoreFileInputStream.close();
                keyStoreFileInputStream = null;
            }
        }
    }

    private void createInternalKeystore(KeystoreConfig keystoreConfig) throws KeyStoreException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        String javaHome = System.getProperty("java.home");
        String keytool = javaHome + File.separator + "bin" + File.separator + "keytool";
        String[] args = { keytool, "-genkey", "-dname", getDName(keystoreConfig), "-alias",
                keystoreConfig.getAlias(), "-keystore", keystoreConfig.getFilePath(), "-storepass",
                keystoreConfig.getFilePassword(), "-keypass", keystoreConfig.getFilePassword(), "-keyalg", "RSA",
                "-validity", "3650" //10 years
        };

        int timeout = 5 * 60 * 1000; //5min
        ExecuteWatchdog wdog = new ExecuteWatchdog(timeout);
        Execute exec = new Execute(new PumpStreamHandler(output), wdog);

        exec.setCommandline(args);

        //TODO shouldn't have password in log
        log.debug("Generating keystore: " + keystoreConfig.getFilePath());

        int rc;

        try {
            rc = exec.execute();
        } catch (Exception e) {
            rc = -1;
            log.error(e);
        }

        if (rc != 0) {
            String msg = output.toString().trim();

            if (msg.length() == 0) {
                msg = "timeout after " + timeout + "ms";
            }

            // TODO This is super fugly but considering how we're creating the keystore file, there isn't a clean way of accomplishing this
            //      Basically, there is a small window of opportunity where two agent processes could discover no keystore file and try to
            //      generate one using the ExceuteWatchdog.  One will succeed, the other will fail, if that happens we shouldn't kill the process.  
            //      For any other exception throw it...
            if (!msg.toLowerCase().contains("key pair not generated, alias <"
                    + keystoreConfig.getAlias().toLowerCase() + "> already exists")) {
                //can't have password in log
                throw new KeyStoreException("Failed to create keystore:" + keystoreConfig.getAlias() + ", " + msg);
            }
        }
    }

    public X509TrustManager getCustomTrustManager(X509TrustManager defaultTrustManager,
            KeystoreConfig keystoreConfig, boolean acceptUnverifiedCertificates, KeyStore trustStore) {
        return new CustomTrustManager(defaultTrustManager, keystoreConfig, acceptUnverifiedCertificates, trustStore,
                isDB.get());
    }

    private class CustomTrustManager implements X509TrustManager {
        private final Log log = LogFactory.getLog(X509TrustManager.class);
        private final X509TrustManager defaultTrustManager;
        private final KeystoreConfig keystoreConfig;
        private final boolean acceptUnverifiedCertificates;
        private final KeyStore trustStore;
        private final boolean isDB;

        private CustomTrustManager(X509TrustManager defaultTrustManager, KeystoreConfig keystoreConfig,
                boolean acceptUnverifiedCertificates, KeyStore trustStore, boolean isDB) {
            this.defaultTrustManager = defaultTrustManager;
            this.keystoreConfig = keystoreConfig;
            this.acceptUnverifiedCertificates = acceptUnverifiedCertificates;
            this.trustStore = trustStore;
            this.isDB = isDB;
        }

        public X509Certificate[] getAcceptedIssuers() {
            return defaultTrustManager.getAcceptedIssuers();
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            try {
                defaultTrustManager.checkServerTrusted(chain, authType);
            } catch (CertificateException e) {
                CertificateExpiredException expiredCertException = getCertExpiredException(e);
                if (expiredCertException != null) {
                    log.error("Fail the connection because received certificate is expired. "
                            + "Please update the certificate.", expiredCertException);
                    throw new CertificateException(e);
                }
                if (acceptUnverifiedCertificates) {
                    log.info("Import the certification. (Received certificate is not trusted by keystore)");
                    importCertificate(chain);
                } else {
                    log.warn("Fail the connection because received certificate is not trusted by "
                            + "keystore: alias=" + keystoreConfig.getAlias());
                    log.debug("Fail the connection because received certificate is not trusted by "
                            + "keystore: alias=" + keystoreConfig.getAlias() + ", acceptUnverifiedCertificates="
                            + acceptUnverifiedCertificates, e);
                    throw new CertificateException(e);
                }
            }
        }

        private CertificateExpiredException getCertExpiredException(Exception e) {
            while (e != null) {
                if (e instanceof CertificateExpiredException) {
                    return (CertificateExpiredException) e;
                }
                e = (Exception) e.getCause();
            }
            return null;
        }

        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            defaultTrustManager.checkClientTrusted(chain, authType);
        }

        private void importCertificate(X509Certificate[] chain) throws CertificateException {
            FileOutputStream ksFileOutputStream = null;
            final boolean debug = log.isDebugEnabled();
            final StopWatch watch = new StopWatch();
            try {
                for (X509Certificate cert : chain) {
                    String[] cnValues = AbstractVerifier.getCNs(cert);
                    String alias = (cnValues != null && cnValues.length > 0) ? cnValues[0] : "UnknownCN";
                    alias += "-ts=" + System.currentTimeMillis();
                    trustStore.setCertificateEntry(alias, cert);
                }
                if (!isDB) {
                    ksFileOutputStream = new FileOutputStream(keystoreConfig.getFilePath());
                    trustStore.store(ksFileOutputStream, keystoreConfig.getFilePassword().toCharArray());
                }
            } catch (FileNotFoundException e) {
                // Can't find the keystore in the path
                log.error("Can't find the keystore in " + keystoreConfig.getFilePath() + ". Error message: " + e,
                        e);
            } catch (NoSuchAlgorithmException e) {
                log.error("The algorithm is not supported. Error message: " + e, e);
            } catch (Exception e) {
                // expect KeyStoreException, IOException
                log.error("Exception when trying to import certificate: " + e, e);
            } finally {
                close(ksFileOutputStream);
                ksFileOutputStream = null;
                if (debug)
                    log.debug("importCert: " + watch);
            }
        }

        private void close(FileOutputStream fos) {
            if (fos == null) {
                return;
            }
            try {
                fos.close();
            } catch (IOException e) {
            }
        }
    }
}