diff --git a/rexster-core/src/main/java/com/tinkerpop/rexster/util/RexsterSslHelper.java b/rexster-core/src/main/java/com/tinkerpop/rexster/util/RexsterSslHelper.java
new file mode 100644
index 00000000..c23a2fdd
--- /dev/null
+++ b/rexster-core/src/main/java/com/tinkerpop/rexster/util/RexsterSslHelper.java
@@ -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;
+ }
+}
diff --git a/rexster-server/config/rexster.xml b/rexster-server/config/rexster.xml
index a065321c..782243b0 100644
--- a/rexster-server/config/rexster.xml
+++ b/rexster-server/config/rexster.xml
@@ -7,6 +7,7 @@
public
UTF-8
false
+ false
true
2097152
8192
@@ -67,6 +68,23 @@
+
+ TLS
+ JKS
+ JKS
+
+ config/ssl/serverKeyStore.jks
+
+
+
+ SunX509
+
+
+ SunX509
+
+ false
+ false
+
jmx
diff --git a/rexster-server/src/main/java/com/tinkerpop/rexster/server/HttpRexsterServer.java b/rexster-server/src/main/java/com/tinkerpop/rexster/server/HttpRexsterServer.java
index e589ad78..9f633d3c 100644
--- a/rexster-server/src/main/java/com/tinkerpop/rexster/server/HttpRexsterServer.java
+++ b/rexster-server/src/main/java/com/tinkerpop/rexster/server/HttpRexsterServer.java
@@ -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;
@@ -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.
@@ -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;
}
@@ -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!");
+ }
}