Skip to content

Commit

Permalink
Added SSL support to RexsterHttpServer
Browse files Browse the repository at this point in the history
  • Loading branch information
alszeb committed Dec 18, 2014
1 parent 1dd23e3 commit 80e8544
Show file tree
Hide file tree
Showing 3 changed files with 298 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
package com.tinkerpop.rexster.util;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import org.apache.commons.configuration.Configuration;
import org.apache.log4j.Logger;

/**
* Assists in various parts of SSL setup for Rexster, particularly creating the SSLContext from a configuration. The
* default configuration is for clients relying on the default JVM TrustStore, it is expected that servers will specify
* at least some SSL properties.
*/
public class RexsterSslHelper {
public static final String KEY_HTTP_SSL_ENABLED = "http.enable-ssl";
public static final String KEY_SSL_PROTOCOL = "ssl.protocol";
public static final String KEY_SSL_TRUST_STORE = "ssl.trust-store";
public static final String KEY_SSL_TRUST_STORE_PASSWORD = "ssl.trust-store-password";
public static final String KEY_SSL_TRUST_STORE_PROVIDER = "ssl.trust-store-provider";
public static final String KEY_SSL_TRUST_MANAGER_FACTORY_ALGORITHM = "ssl.trust-manager-factory.algorithm";
public static final String KEY_SSL_KEY_STORE = "ssl.key-store";
public static final String KEY_SSL_KEY_STORE_PASSWORD = "ssl.key-store-password";
public static final String KEY_SSL_KEY_STORE_PROVIDER = "ssl.key-store-provider";
public static final String KEY_SSL_KEY_MANAGER_FACTORY_ALGORITHM = "ssl.key-manager-factory.algorithm";
public static final String KEY_SSL_NEED_CLIENT_AUTH = "ssl.need-client-auth";
public static final String KEY_SSL_WANT_CLIENT_AUTH = "ssl.want-client-auth";
private static final Logger logger = Logger.getLogger(RexsterSslHelper.class);
private static final String DEFAULT_STORE_PROVIDER = "JKS";
private static final String DEFAULT_SSL_PROTOCOL = "TLS";

/**
* An empty String. Not specifying a keystore results in no keystore being used. Not having a keystore is
* appropriate for clients when client-auth is disabled, in which case only a truststore is needed.
*/
private static final String DEFAULT_KEY_STORE_PATH = "";

/**
* No default keystore password.
*/
private static final char[] DEFAULT_KEY_STORE_PASSWORD = null;

/**
* Use the default JVM truststore if omit.
*/
private static final String DEFAULT_TRUST_STORE_PATH = System.getenv("JAVA_HOME") + "/jre/lib/security/cacerts";

/**
* Standard JVM default truststore password.
*/
private static final String DEFAULT_TRUST_STORE_PASSWORD = "changeit";

/**
* Configuration this class utilizes for it's SSL functions.
*/
private final Configuration configuration;

/**
* Constructor.
*
* @param configuration Configuration which includes SSL properties.
*/
public RexsterSslHelper(Configuration configuration) {
this.configuration = configuration;
}

/**
* Logs a message and throws an SSLException with the same message.
*
* @param msg The message to log and pass into the exception.
* @param e The exception to throw with the message.
* @throws SSLException Rethrown exception indicating the cause exception was SSL-related.
*/
private static void logAndRethrow(String msg, Exception e) throws SSLException {
logger.error(msg, e);
throw new SSLException(msg, e);
}

/**
* Creates an {@link javax.net.ssl.SSLContext} object using {@code configuration}.
*
* @return An SSLContext object that can be used for securing Rexster servers with SSL.
* @throws SSLException If a variety of SSL related exceptions occur.
*/
public SSLContext createRexsterSslContext() throws SSLException {
String rexsterHome = System.getenv("REXSTER_HOME");
rexsterHome = rexsterHome == null ? "" : rexsterHome;

logger.info("Creating SSLContext.");
final char[] secretServerPassword = getKeyStorePassword();

TrustManagerFactory tmf = null;
KeyManagerFactory kmf = null;

try {
tmf = initTrustManagerFactory(rexsterHome);
// Keystore only used if it has been specified.
if (!getKeyStore().isEmpty()) {
kmf = initKeyManagerFactory(rexsterHome, secretServerPassword);
}
} catch (final IOException e) {
logAndRethrow("Problem loading KeyStore files!", e);
} catch (final CertificateException e) {
logAndRethrow("Problem with certificate while loading KeyStore!", e);
} catch (final NoSuchAlgorithmException e) {
logAndRethrow("Invalid KeyStore algorithm!", e);
} catch (final UnrecoverableKeyException e) {
logAndRethrow("Problem initializing KeyManagerFactory!", e);
} catch (final KeyStoreException e) {
logAndRethrow("Unable to load Keystore!", e);
}

return initSslContext(tmf, kmf);
}

public final String getSslProtocol() {
return configuration.getString(KEY_SSL_PROTOCOL, DEFAULT_SSL_PROTOCOL);
}

public final String getTrustStore() {
return configuration.getString(KEY_SSL_TRUST_STORE, DEFAULT_TRUST_STORE_PATH);
}

public final char[] getTrustStorePassword() {
return configuration.getString(KEY_SSL_TRUST_STORE_PASSWORD, DEFAULT_TRUST_STORE_PASSWORD).toCharArray();
}

public final String getTrustStoreProvider() {
return configuration.getString(KEY_SSL_TRUST_STORE_PROVIDER, DEFAULT_STORE_PROVIDER);
}

public final String getKeyStore() {
return configuration.getString(KEY_SSL_KEY_STORE, DEFAULT_KEY_STORE_PATH);
}

public final String getKeyStoreProvider() {
return configuration.getString(KEY_SSL_KEY_STORE_PROVIDER, DEFAULT_STORE_PROVIDER);
}

public final boolean getNeedClientAuth() {
return configuration.getBoolean(KEY_SSL_NEED_CLIENT_AUTH, false);
}

public final boolean getWantClientAuth() {
return configuration.getBoolean(KEY_SSL_WANT_CLIENT_AUTH, false);
}

private char[] getKeyStorePassword() {
final String keyStorePassword = configuration.getString(KEY_SSL_KEY_STORE_PASSWORD, null);
if (keyStorePassword == null) {
return DEFAULT_KEY_STORE_PASSWORD;
}
return keyStorePassword.toCharArray();
}

/**
* Initializes the SSL Context based on {@code configuration};
*
* @param trustManagerFactory Provides trust managers to use with the produced SSLContext, if any.
* @param keyManagerFactory Provides key managers to use with the produced SSLContext, if any.
* @return an SSLContext based on this class' configuration.
* @throws SSLException If a variety of SSL related errors occur.
*/
private SSLContext initSslContext(TrustManagerFactory trustManagerFactory, KeyManagerFactory keyManagerFactory)
throws SSLException {
SSLContext sslContext = null;
final String sslProtocol = getSslProtocol();

KeyManager[] keyManagers = null;

if (keyManagerFactory != null) {
keyManagers = keyManagerFactory.getKeyManagers();
}
final TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
try {
sslContext = SSLContext.getInstance(sslProtocol);
sslContext.init(keyManagers, trustManagers, null);
} catch (final NoSuchAlgorithmException e) {
logAndRethrow(String.format("Invalid SSL Protocol '%s'", sslProtocol), e);
} catch (final KeyManagementException e) {
logAndRethrow("Unable to initialize SSLContext.", e);
}
return sslContext;
}

private TrustManagerFactory initTrustManagerFactory(String rexsterHome)
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
final String tmfAlgorithm = this.configuration.getString(
KEY_SSL_TRUST_MANAGER_FACTORY_ALGORITHM, TrustManagerFactory.getDefaultAlgorithm());
final TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);

final KeyStore trustStore = KeyStore.getInstance(getTrustStoreProvider());
InputStream trustStoreInputStream = null;

final String trustStorePath = getTrustStore();

// If truststore is intentionally blank, do not use a truststore.
if (!trustStorePath.isEmpty()) {
try {
trustStoreInputStream = new FileInputStream(rexsterHome + trustStorePath);
trustStore.load(trustStoreInputStream, getTrustStorePassword());
} finally {
if (trustStoreInputStream != null) {
trustStoreInputStream.close();
}
}
}
tmf.init(trustStore);

return tmf;
}

private KeyManagerFactory initKeyManagerFactory(
String rexsterHome, char[] secretServerPassword)
throws NoSuchAlgorithmException, IOException, CertificateException, UnrecoverableKeyException,
KeyStoreException {
final String kmfAlgorithm = this.configuration.getString(
KEY_SSL_KEY_MANAGER_FACTORY_ALGORITHM, KeyManagerFactory.getDefaultAlgorithm());

final KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgorithm);

final KeyStore keyStore = KeyStore.getInstance(getKeyStoreProvider());
final String keyStorePath = getKeyStore();
InputStream keyStoreInputStream = null;

try {
keyStoreInputStream = new FileInputStream(rexsterHome + keyStorePath);
keyStore.load(keyStoreInputStream, secretServerPassword);
kmf.init(keyStore, secretServerPassword);
} finally {
if (keyStoreInputStream != null) {
keyStoreInputStream.close();
}
}

return kmf;
}
}
18 changes: 18 additions & 0 deletions rexster-server/config/rexster.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<web-root>public</web-root>
<character-set>UTF-8</character-set>
<enable-jmx>false</enable-jmx>
<enable-ssl>false</enable-ssl>
<enable-doghouse>true</enable-doghouse>
<max-post-size>2097152</max-post-size>
<max-header-size>8192</max-header-size>
Expand Down Expand Up @@ -67,6 +68,23 @@
</configuration>
</authentication>
</security>
<ssl>
<protocol>TLS</protocol>
<trust-store-provider>JKS</trust-store-provider>
<key-store-provider>JKS</key-store-provider>
<trust-store></trust-store>
<key-store>config/ssl/serverKeyStore.jks</key-store>
<trust-store-password></trust-store-password>
<key-store-password></key-store-password>
<key-manager-factory>
<algorithm>SunX509</algorithm>
</key-manager-factory>
<trust-manager-factory>
<algorithm>SunX509</algorithm>
</trust-manager-factory>
<need-client-auth>false</need-client-auth>
<want-client-auth>false</want-client-auth>
</ssl>
<metrics>
<reporter>
<type>jmx</type>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import com.tinkerpop.rexster.servlet.DogHouseServlet;
import com.tinkerpop.rexster.servlet.EvaluatorServlet;
import com.tinkerpop.rexster.servlet.RexsterStaticHttpHandler;
import com.tinkerpop.rexster.util.RexsterSslHelper;

import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.log4j.Level;
Expand All @@ -37,13 +39,21 @@
import org.glassfish.grizzly.http.server.ServerConfiguration;
import org.glassfish.grizzly.servlet.ServletRegistration;
import org.glassfish.grizzly.servlet.WebappContext;
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
import org.glassfish.grizzly.threadpool.GrizzlyExecutorService;
import org.glassfish.grizzly.threadpool.ThreadPoolConfig;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.net.ssl.SSLException;
import javax.ws.rs.core.Context;
import java.io.File;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

/**
* Initializes the HTTP server for Rexster serving REST and Dog House.
Expand Down Expand Up @@ -482,6 +492,11 @@ private void configureNetworkListener() throws Exception {
NetworkListener listener = this.httpServer.getListener("grizzly");
if (listener == null) {
listener = new NetworkListener("grizzly", rexsterServerHost, rexsterServerPort);

if (properties.getConfiguration().getBoolean(RexsterSslHelper.KEY_HTTP_SSL_ENABLED, false)) {
secureWithSsl(listener);
}

this.httpServer.addListener(listener);
allowPortChange = false;
}
Expand Down Expand Up @@ -528,4 +543,18 @@ private void configureNetworkListener() throws Exception {
logger.info(String.format("Using %s IOStrategy for HTTP/REST.", strategy.getClass().getName()));
}
}

private void secureWithSsl(NetworkListener listener) throws SSLException {
logger.info("Attempting to secure HttpRexsterServer with SSL...");
RexsterSslHelper rexsterSslHelper = new RexsterSslHelper(properties.getConfiguration());
final SSLEngineConfigurator configurator =
new SSLEngineConfigurator(rexsterSslHelper.createRexsterSslContext());

configurator.setNeedClientAuth(rexsterSslHelper.getNeedClientAuth()).setWantClientAuth(
rexsterSslHelper.getWantClientAuth()).setClientMode(false);

listener.setSecure(true);
listener.setSSLEngineConfig(configurator);
logger.info("HttpRexsterServer successfully secured with SSL!");
}
}

0 comments on commit 80e8544

Please sign in to comment.