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

Java tutorial

Introduction

Here is the source code for org.hyperic.util.security.DatabaseSSLProviderImpl.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) [2004-2011], 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.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.X509Certificate;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
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.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.hyperic.util.timer.StopWatch;

public class DatabaseSSLProviderImpl implements SSLProvider {

    private SSLContext sslContext;
    private SSLSocketFactory sslSocketFactory;
    private static final Log log = LogFactory.getLog(DefaultSSLProviderImpl.class);
    private static final ReadWriteLock KEYSTORE_LOCK = new ReentrantReadWriteLock();
    private static final Lock KEYSTORE_READER_LOCK = KEYSTORE_LOCK.readLock();
    private static final Lock KEYSTORE_WRITER_LOCK = KEYSTORE_LOCK.writeLock();

    private KeyManagerFactory getKeyManagerFactory(final KeyStore keystore, final String password)
            throws KeyStoreException {
        try {
            KeyManagerFactory keyManagerFactory = KeyManagerFactory
                    .getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keystore, password.toCharArray());
            return keyManagerFactory;
        } catch (NoSuchAlgorithmException e) {
            // no support for algorithm, if this happens we're kind of screwed
            // we're using the default so it should never happen
            throw new KeyStoreException("The algorithm is not supported. Error message:" + e.getMessage());
        } catch (UnrecoverableKeyException e) {
            // invalid password, should never happen
            throw new KeyStoreException("Password for the keystore is invalid. Error message:" + e.getMessage());
        }
    }

    private TrustManagerFactory getTrustManagerFactory(final KeyStore keystore)
            throws KeyStoreException, IOException {
        try {
            TrustManagerFactory trustManagerFactory = TrustManagerFactory
                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keystore);
            return trustManagerFactory;
        } catch (NoSuchAlgorithmException e) {
            // no support for algorithm, if this happens we're kind of screwed
            // we're using the default so it should never happen
            log.error("The algorithm is not supported. Error message:" + e.getMessage());
            throw new KeyStoreException(e);
        }
    }

    public DatabaseSSLProviderImpl(KeystoreConfig keystoreConfig, boolean acceptUnverifiedCertificates) {
        if (log.isDebugEnabled()) {
            log.debug("Keystore info: alias=" + keystoreConfig.getAlias() + ", path:" + keystoreConfig.getFilePath()
                    + ", acceptUnverifiedCertificates=" + acceptUnverifiedCertificates);
        }
        boolean hasLock = false;
        final boolean debug = log.isDebugEnabled();
        final StopWatch watch = new StopWatch();
        try {
            KeystoreManager keystoreMgr = KeystoreManager.getKeystoreManager();
            KEYSTORE_READER_LOCK.lockInterruptibly();
            hasLock = true;
            KeyStore trustStore = keystoreMgr.getKeyStore(keystoreConfig);
            KeyManagerFactory keyManagerFactory = getKeyManagerFactory(trustStore,
                    keystoreConfig.getFilePassword());
            TrustManagerFactory trustManagerFactory = getTrustManagerFactory(trustStore);
            X509TrustManager defaultTrustManager = (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
            X509TrustManager customTrustManager = getCustomTrustManager(defaultTrustManager, keystoreConfig,
                    acceptUnverifiedCertificates, trustStore);
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagerFactory.getKeyManagers(), new TrustManager[] { customTrustManager },
                    new SecureRandom());
            // XXX Should we use ALLOW_ALL_HOSTNAME_VERIFIER (least restrictive) or 
            //     BROWSER_COMPATIBLE_HOSTNAME_VERIFIER (moderate restrictive) or
            //     STRICT_HOSTNAME_VERIFIER (most restrictive)???
            sslSocketFactory = new SSLSocketFactory(sslContext, getHostnameVerifier());
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            if (hasLock)
                KEYSTORE_READER_LOCK.unlock();
            if (debug)
                log.debug("readCert: " + watch);
        }
    }

    private X509HostnameVerifier getHostnameVerifier() {
        return new X509HostnameVerifier() {
            private AllowAllHostnameVerifier internalVerifier = new AllowAllHostnameVerifier();

            public boolean verify(String host, SSLSession session) {
                return internalVerifier.verify(host, session);
            }

            public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
                internalVerifier.verify(host, cns, subjectAlts);
            }

            public void verify(String host, X509Certificate cert) throws SSLException {
                internalVerifier.verify(host, cert);
            }

            public void verify(String host, SSLSocket ssl) throws IOException {
                try {
                    internalVerifier.verify(host, ssl);
                } catch (SSLPeerUnverifiedException e) {
                    throw new SSLPeerUnverifiedException(
                            "The authenticity of host '" + host + "' can't be established.");
                }
            }
        };
    }

    private X509TrustManager getCustomTrustManager(final X509TrustManager defaultTrustManager,
            final KeystoreConfig keystoreConfig, final boolean acceptUnverifiedCertificates,
            final KeyStore trustStore) {
        return new X509TrustManager() {
            private final Log log = LogFactory.getLog(X509TrustManager.class);

            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() + ", path=" + keystoreConfig.getFilePath());
                        log.debug(
                                "Fail the connection because received certificate is not trusted by keystore: alias="
                                        + keystoreConfig.getAlias() + ", path=" + keystoreConfig.getFilePath()
                                        + ", 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 keyStoreFileOutputStream = null;
                boolean hasLock = false;
                final boolean debug = log.isDebugEnabled();
                final StopWatch watch = new StopWatch();
                try {
                    for (X509Certificate cert : chain) {
                        String[] cnValues = AbstractVerifier.getCNs(cert);
                        String alias;

                        if (cnValues != null && cnValues.length > 0) {
                            alias = cnValues[0];
                        } else {
                            alias = "UnknownCN";
                        }

                        alias += "-ts=" + System.currentTimeMillis();

                        trustStore.setCertificateEntry(alias, cert);
                    }
                    KEYSTORE_WRITER_LOCK.lockInterruptibly();
                    hasLock = true;
                    keyStoreFileOutputStream = new FileOutputStream(keystoreConfig.getFilePath());
                    trustStore.store(keyStoreFileOutputStream, 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.getMessage(), e);
                } catch (NoSuchAlgorithmException e) {
                    log.error("The algorithm is not supported. Error message:" + e.getMessage(), e);
                } catch (Exception e) {
                    // expect KeyStoreException, IOException
                    log.error("Exception when trying to import certificate: " + e.getMessage(), e);
                } finally {
                    close(keyStoreFileOutputStream);
                    keyStoreFileOutputStream = null;
                    if (hasLock) {
                        KEYSTORE_WRITER_LOCK.unlock();
                    }
                    if (debug)
                        log.debug("importCert: " + watch);
                }
            }

            private void close(FileOutputStream keyStoreFileOutputStream) {
                if (keyStoreFileOutputStream != null) {
                    try {
                        keyStoreFileOutputStream.close();
                    } catch (IOException e) {
                        log.error(e, e);
                    }
                }
            }
        };
    }

    public SSLContext getSSLContext() {
        return sslContext;
    }

    public SSLSocketFactory getSSLSocketFactory() {
        return sslSocketFactory;
    }
}