be.solidx.hot.nio.HttpsClient.java Source code

Java tutorial

Introduction

Here is the source code for be.solidx.hot.nio.HttpsClient.java

Source

package be.solidx.hot.nio;

/*
 * #%L
 * Hot
 * %%
 * Copyright (C) 2010 - 2016 Solidx
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * 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, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.Map;
import java.util.concurrent.ExecutorService;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.io.IOUtils;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.handler.codec.http.HttpClientCodec;
import org.jboss.netty.handler.codec.http.HttpContentDecompressor;
import org.jboss.netty.handler.logging.LoggingHandler;
import org.jboss.netty.handler.ssl.SslHandler;
import org.springframework.core.convert.support.DefaultConversionService;

import be.solidx.hot.utils.SSLUtils;

public abstract class HttpsClient<CLOSURE, MAP> extends HttpClient<CLOSURE, MAP> {

    private static final String JKS = "jks";
    private static final String JKSPASSWORD = "jksPassword";
    private static final String JKS_CERTIFICATE_PASSWORD = "jksCertificatePassword";
    private static final String P12 = "p12";
    private static final String KEY = "key";
    private static final String PASSPHRASE = "passphrase";
    private static final String CERT = "cert";
    private static final String CA = "ca";
    private static final String REJECTUNAUTHORIZED = "rejectUnauthorized";
    private static final String SECUREPROTOCOL = "secureProtocol";

    public HttpsClient(ExecutorService eventLoopPool, DefaultConversionService defaultConversionService) {
        super(eventLoopPool, defaultConversionService);
    }

    public HttpsClient(ExecutorService bossExecutorService, ExecutorService eventLoopPool,
            DefaultConversionService defaultConversionService) {
        super(bossExecutorService, eventLoopPool, defaultConversionService);
    }

    protected abstract class HttpsRequest extends Request {

        public HttpsRequest(Map<String, Object> options, CLOSURE requestClosure) {
            super(options, requestClosure);
        }

        @Override
        protected ChannelPipelineFactory buildChannelPipelineFactory() {
            return new ChannelPipelineFactory() {

                @Override
                public ChannelPipeline getPipeline() throws Exception {
                    SSLEngine sslEngine = buildSSLContext(options).createSSLEngine();
                    sslEngine.setUseClientMode(true);

                    ChannelPipeline pipeline = Channels.pipeline();
                    pipeline.addLast("log", new LoggingHandler());
                    pipeline.addLast("ssl", new SslHandler(sslEngine));
                    pipeline.addLast("codec", new HttpClientCodec());
                    pipeline.addLast("inflater", new HttpContentDecompressor());
                    pipeline.addLast("handler", new HotSimpleChannelUpstreamHandler(response));
                    return pipeline;
                }
            };
        }
    }

    protected SSLContext buildSSLContext(Map<String, Object> options) throws SSLContextInitializationException {

        KeyManager[] keyManagers = null;
        TrustManager[] trustManagers = null;

        try {
            String secureProtocol = options.get(SECUREPROTOCOL).toString();
            SSLContext sslContext = SSLContext.getInstance(secureProtocol);

            if (options.get(JKS) != null) {
                keyManagers = handleClientKeystoreURLProvided(options).getKeyManagers();
            } else if (options.get(P12) != null) {
                keyManagers = handleClientPFXURLProvided(options).getKeyManagers();
            } else if (options.get(KEY) != null) {
                keyManagers = handleClientKeyCertURLProvided(options).getKeyManagers();
            }
            trustManagers = handleTrustManagers(options);
            sslContext.init(keyManagers, trustManagers, null);
            return sslContext;
        } catch (KeyManagementException | NoSuchAlgorithmException | SSLContextInitializationException
                | CertificateException | IOException | URISyntaxException e) {
            throw new SSLContextInitializationException(e);
        }
    }

    @Override
    protected OptionsMapper buildOptionsMapper() {
        return new SSLOptionsMapper();
    }

    private TrustManager[] handleTrustManagers(Map<String, Object> options)
            throws CertificateException, IOException, URISyntaxException {
        boolean rejectUnauthorized = (boolean) options.get(REJECTUNAUTHORIZED);
        if (options.get(CA) != null) {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            return new TrustManager[] { new TrustManager(
                    (X509Certificate) certificateFactory
                            .generateCertificate(getInputStream(new URI(options.get(CA).toString()))),
                    rejectUnauthorized) };
        } else if (!rejectUnauthorized) {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            return new TrustManager[] { new TrustManager(null, rejectUnauthorized) };
        }
        return null;
    }

    private KeyManagerFactory handleClientKeystoreURLProvided(Map<String, Object> options)
            throws SSLContextInitializationException {

        InputStream jksFileInputStream = null;
        try {
            KeyManagerFactory keyManagerFactory = null;

            jksFileInputStream = getInputStream(new URI(options.get(JKS).toString()));
            KeyStore keyStore = KeyStore.getInstance(JKS);
            keyStore.load(jksFileInputStream, options.get(JKSPASSWORD).toString().toCharArray());

            keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
            keyManagerFactory.init(keyStore, options.get(JKS_CERTIFICATE_PASSWORD).toString().toCharArray());

            return keyManagerFactory;
        } catch (UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException | CertificateException
                | URISyntaxException | IOException e) {
            throw new SSLContextInitializationException(e);
        } finally {
            if (jksFileInputStream != null) {
                try {
                    jksFileInputStream.close();
                } catch (IOException e) {
                }
            }
        }
    }

    private KeyManagerFactory handleClientPFXURLProvided(Map<String, Object> options)
            throws SSLContextInitializationException {

        InputStream jksFileInputStream = null;
        try {
            KeyManagerFactory keyManagerFactory = null;

            jksFileInputStream = getInputStream(new URI(options.get(P12).toString()));
            KeyStore keyStore = KeyStore.getInstance("pkcs12", "SunJSSE");
            keyStore.load(jksFileInputStream, options.get(PASSPHRASE).toString().toCharArray());

            keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
            keyManagerFactory.init(keyStore, options.get(PASSPHRASE).toString().toCharArray());
            return keyManagerFactory;
        } catch (UnrecoverableKeyException | KeyStoreException | NoSuchProviderException | NoSuchAlgorithmException
                | CertificateException | URISyntaxException | IOException e) {
            throw new SSLContextInitializationException(e);
        } finally {
            if (jksFileInputStream != null) {
                try {
                    jksFileInputStream.close();
                } catch (IOException e) {
                }
            }
        }
    }

    private KeyManagerFactory handleClientKeyCertURLProvided(Map<String, Object> options)
            throws SSLContextInitializationException {

        InputStream keyInputStream = null;
        InputStream certInputStream = null;

        try {
            KeyManagerFactory keyManagerFactory = null;
            KeyStore keyStore = KeyStore.getInstance(JKS);
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

            byte[] key = IOUtils.toByteArray(getInputStream(new URI(options.get(KEY).toString())));
            Certificate certCertificate = certificateFactory
                    .generateCertificate(getInputStream(new URI(options.get(CERT).toString())));
            char[] password = options.get(PASSPHRASE).toString().toCharArray();

            keyStore.load(null, null);
            // No CA needed, just add pivate key and associated public key to a keystore
            keyStore.setKeyEntry(KEY, SSLUtils.toPrivateKey(new ByteArrayInputStream(key)), password,
                    new Certificate[] { certCertificate });

            keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
            keyManagerFactory.init(keyStore, password);

            return keyManagerFactory;
        } catch (NullPointerException | UnrecoverableKeyException | KeyStoreException | CertificateException
                | NoSuchAlgorithmException | IOException | URISyntaxException | InvalidKeySpecException e) {
            throw new SSLContextInitializationException(e);
        } finally {
            if (keyInputStream != null) {
                try {
                    keyInputStream.close();
                } catch (IOException e) {
                }
            }
            if (certInputStream != null) {
                try {
                    certInputStream.close();
                } catch (IOException e) {
                }
            }
        }
    }

    private InputStream getInputStream(URI uri) throws IOException {
        InputStream jksFileInputStream;
        if (uri.getScheme() != null && uri.equals("file")) {
            jksFileInputStream = new FileInputStream(new File(uri));
        } else {
            if (uri.isAbsolute()) {
                jksFileInputStream = getClass().getResourceAsStream(uri.getPath());
            } else {
                jksFileInputStream = getClass().getClassLoader().getResourceAsStream(uri.getPath());
            }
        }
        return jksFileInputStream;
    }

    private static class TrustManager implements X509TrustManager {

        X509Certificate x509Certificate;
        boolean rejectUnauthorized;

        public TrustManager(X509Certificate x509Certificate, boolean rejectUnauthorized) {
            this.x509Certificate = x509Certificate;
            this.rejectUnauthorized = rejectUnauthorized;
        }

        // Unneeded because no client auth
        @Override
        public void checkClientTrusted(X509Certificate[] arg0, String authType) throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        @Override
        public void checkServerTrusted(X509Certificate[] serverCertificates, String authType)
                throws CertificateException {
            if (!rejectUnauthorized)
                return;

            if (serverCertificates.length == 0) {
                throw new CertificateException("Server didn't send any certificate");
            }
            // verify each certificate against CA certificate to find a valid one
            boolean found = false;
            for (X509Certificate x509Certificate : serverCertificates) {
                try {
                    x509Certificate.verify(this.x509Certificate.getPublicKey());
                    found = true;
                    break;
                } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchProviderException
                        | SignatureException e) {
                    throw new CertificateException(e);
                }
            }
            if (!found) {
                throw new CertificateException("None of the provided certificates can be validated");
            }
        }
    }

    public class SSLOptionsMapper extends OptionsMapper {
        Map<String, Object> toNettyOptions(Map<String, Object> options) {
            Map<String, Object> nettyOptions = super.toNettyOptions(options);

            if (options.get(CA) != null) {
                nettyOptions.put(CA, options.get(CA));
            }
            if (options.get(CERT) != null) {
                nettyOptions.put(CERT, options.get(CERT));
            }
            if (options.get(JKS) != null) {
                nettyOptions.put(JKS, options.get(JKS));
            }
            if (options.get(JKS_CERTIFICATE_PASSWORD) != null) {
                nettyOptions.put(JKS_CERTIFICATE_PASSWORD, options.get(JKS_CERTIFICATE_PASSWORD));
            }
            if (options.get(JKSPASSWORD) != null) {
                nettyOptions.put(JKSPASSWORD, options.get(JKSPASSWORD));
            }
            if (options.get(KEY) != null) {
                nettyOptions.put(KEY, options.get(KEY));
            }
            if (options.get(PASSPHRASE) != null) {
                nettyOptions.put(PASSPHRASE, options.get(PASSPHRASE));
            }
            if (options.get(P12) != null) {
                nettyOptions.put(P12, options.get(P12));
            }
            if (options.get(REJECTUNAUTHORIZED) != null) {
                nettyOptions.put(REJECTUNAUTHORIZED,
                        HttpsClient.this.conversionService.convert(options.get(REJECTUNAUTHORIZED), Boolean.class));
            } else {
                nettyOptions.put(REJECTUNAUTHORIZED, true);
            }
            if (options.get(SECUREPROTOCOL) != null) {
                nettyOptions.put(SECUREPROTOCOL, options.get(SECUREPROTOCOL));
            } else {
                nettyOptions.put(SECUREPROTOCOL, "TLSv1.2");
            }
            return nettyOptions;
        }
    }
}