org.cloudcoder.builder2.server.WebappSocketFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudcoder.builder2.server.WebappSocketFactory.java

Source

// CloudCoder - a web-based pedagogical programming environment
// Copyright (C) 2011-2014, Jaime Spacco <jspacco@knox.edu>
// Copyright (C) 2011-2014, David H. Hovemeyer <dhovemey@ycp.edu>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package org.cloudcoder.builder2.server;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.io.IOUtils;
import org.cloudcoder.builder2.server.Builder2Daemon.Options;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A WebappSocketFactory object creates secure connections to the webapp.
 * 
 * @author Jaime Spacco
 * @author David Hovemeyer
 */
public class WebappSocketFactory {
    private static final Logger logger = LoggerFactory.getLogger(WebappSocketFactory.class);

    private Options options;

    /**
     * Constructor.
     * 
     * @param options the {@link Options} describing how to connect to the webapp
     */
    public WebappSocketFactory(Options options) {
        this.options = options;
    }

    private SSLSocketFactory createSocketFactory() throws IOException, GeneralSecurityException {
        String keyStoreType = "JKS";
        String keystoreFilename = options.getKeystoreFilename();
        InputStream keyStoreInputStream = this.getClass().getClassLoader().getResourceAsStream(keystoreFilename);
        if (keyStoreInputStream == null) {
            throw new IOException("Could not load keystore " + keystoreFilename);
        }

        KeyStore keyStore;
        String keystorePassword = options.getKeystorePassword();
        try {
            keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(keyStoreInputStream, keystorePassword.toCharArray());
        } finally {
            IOUtils.closeQuietly(keyStoreInputStream);
        }

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX", "SunJSSE");
        //trustManagerFactory.init(trustStore);
        // XXX Load the cert (public key) here instead of the private key?
        trustManagerFactory.init(keyStore);

        // TrustManager
        X509TrustManager x509TrustManager = null;
        for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
            if (trustManager instanceof X509TrustManager) {
                x509TrustManager = (X509TrustManager) trustManager;
                break;
            }
        }
        if (x509TrustManager == null) {
            throw new IllegalArgumentException("Cannot find x509TrustManager");
        }

        // KeyManager
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509", "SunJSSE");
        keyManagerFactory.init(keyStore, keystorePassword.toCharArray());
        X509KeyManager x509KeyManager = null;
        for (KeyManager keyManager : keyManagerFactory.getKeyManagers()) {
            if (keyManager instanceof X509KeyManager) {
                x509KeyManager = (X509KeyManager) keyManager;
                break;
            }
        }
        if (x509KeyManager == null) {
            throw new NullPointerException();
        }

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(new KeyManager[] { x509KeyManager }, new TrustManager[] { x509TrustManager }, null);

        return sslContext.getSocketFactory();
    }

    /**
     * Create a connection to the webapp.
     * Note that a connection could be directly secured by SSL/TLS,
     * or indirectly secured by an ssh tunnel, or both. 
     * 
     * @return ISocket through which the builder can communicate with the webapp
     * @throws UnknownHostException
     * @throws IOException
     * @throws GeneralSecurityException
     */
    public ISocket connectToWebapp() throws UnknownHostException, IOException, GeneralSecurityException {
        // Create a socket factory that will create a socket to
        // the webapp (either directly or via the ssh tunnel).
        ISocketFactory socketFactory;
        if (options.useSSL()) {
            socketFactory = new SSLSocketFactoryAdapter(createSocketFactory());
        } else {
            socketFactory = new PlainSocketFactory();
        }

        // Depending on whether or not an ssh tunnel is being created,
        // use the socket factory to create the actual ISocket connection to the webapp.
        if (options.useSshTunnel()) {
            // Communicate over ssh tunnel.
            // This is a bit redundant in the sense that ssh already implements
            // authentication and encryption.  However, we have seen very
            // strange issues creating TLS connections directly to the
            // webapp port, even though ssh seems to work reliably. (?)
            SshTunnelAdapter socket = new SshTunnelAdapter(socketFactory, options.getAppHost(),
                    options.getAppPort(), options.getSshRemoteUser());
            boolean connected = false;
            try {
                socket.connect();
                connected = true;
                return socket;
            } finally {
                if (!connected) {
                    socket.close();
                }
            }
        } else {
            // Open connection directly to webapp port.
            Socket socket = socketFactory.createSocket(options.getAppHost(), options.getAppPort());
            return new SocketAdapter(socket);
        }
    }
}