From 80f04a720eccfaacdc4da32f079ec907ecbe8533 Mon Sep 17 00:00:00 2001 From: Daniel Worthington-Bodart Date: Sun, 25 Oct 2015 14:51:09 +0000 Subject: [PATCH] Got all RestServers to support HTTPS --- .../utterlyidle/ServerConfiguration.java | 46 +++++--- .../handlers/ClientHttpHandler.java | 5 + .../utterlyidle/httpserver/RestHandler.java | 7 +- .../utterlyidle/httpserver/RestServer.java | 18 ++- .../utterlyidle/jetty/RestServer.java | 23 +++- .../utterlyidle/jetty/eclipse/RestServer.java | 17 ++- .../simpleframework/RestServer.java | 3 +- src/com/googlecode/utterlyidle/ssl/SSL.java | 108 ++++++++++++++++++ .../utterlyidle/ssl/SecureString.java | 45 ++++++++ .../utterlyidle/ServerContract.java | 34 +++++- .../handlers/ClientHttpHandlerTest.java | 2 +- .../utterlyidle/ssl/SecureStringTest.java | 18 +++ .../utterlyidle/ssl/generate.keystore.sh | 3 + .../googlecode/utterlyidle/ssl/localhost.jks | Bin 0 -> 1121 bytes 14 files changed, 299 insertions(+), 30 deletions(-) create mode 100644 src/com/googlecode/utterlyidle/ssl/SSL.java create mode 100644 src/com/googlecode/utterlyidle/ssl/SecureString.java create mode 100644 test/com/googlecode/utterlyidle/ssl/SecureStringTest.java create mode 100644 test/com/googlecode/utterlyidle/ssl/generate.keystore.sh create mode 100644 test/com/googlecode/utterlyidle/ssl/localhost.jks diff --git a/src/com/googlecode/utterlyidle/ServerConfiguration.java b/src/com/googlecode/utterlyidle/ServerConfiguration.java index 714c17b5..9c1448bc 100644 --- a/src/com/googlecode/utterlyidle/ServerConfiguration.java +++ b/src/com/googlecode/utterlyidle/ServerConfiguration.java @@ -1,13 +1,17 @@ package com.googlecode.utterlyidle; -import com.googlecode.totallylazy.Unchecked; +import com.googlecode.totallylazy.Option; import com.googlecode.totallylazy.Uri; import com.googlecode.utterlyidle.httpserver.RestServer; +import com.googlecode.utterlyidle.ssl.SSL; +import javax.net.ssl.SSLContext; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Properties; +import static com.googlecode.totallylazy.Option.none; +import static com.googlecode.totallylazy.Option.some; import static com.googlecode.totallylazy.Unchecked.cast; import static com.googlecode.totallylazy.Uri.uri; import static java.lang.Integer.valueOf; @@ -28,33 +32,34 @@ public class ServerConfiguration { public static final String DEFAULT_PORT = "0"; public static final String DEFAULT_CLASS = RestServer.class.getCanonicalName(); - private final String protocol; private final BasePath basePath; private final int maxThreadNumber; private final InetAddress bindAddress; private final int port; private final Class serverClass; + private final Option sslContext; - public ServerConfiguration(String protocol, BasePath basePath, int maxThreadNumber, InetAddress bindAddress, int port, Class serverClass) { - this.protocol = protocol; + public ServerConfiguration(BasePath basePath, int maxThreadNumber, InetAddress bindAddress, int port, Class serverClass, final Option sslContext) { this.basePath = basePath; this.maxThreadNumber = maxThreadNumber; this.bindAddress = bindAddress; this.port = port; this.serverClass = serverClass; + this.sslContext = sslContext; } public ServerConfiguration() { - this(DEFAULT_PROTOCOL, new BasePath(DEFAULT_BASE_PATH), Integer.parseInt(DEFAULT_THREAD_NUMBER), toInetAddress(DEFAULT_BIND_ADDRESS), Integer.parseInt(DEFAULT_PORT), toServer(DEFAULT_CLASS)); + this(new BasePath(DEFAULT_BASE_PATH), Integer.parseInt(DEFAULT_THREAD_NUMBER), toInetAddress(DEFAULT_BIND_ADDRESS), Integer.parseInt(DEFAULT_PORT), toServer(DEFAULT_CLASS), none(SSLContext.class)); } public ServerConfiguration(Properties properties) { - this(properties.getProperty(SERVER_PROTOCOL, DEFAULT_PROTOCOL), + this( new BasePath(properties.getProperty(SERVER_BASE_PATH, DEFAULT_BASE_PATH)), valueOf(properties.getProperty(MAX_THREAD_NUM, DEFAULT_THREAD_NUMBER)), toInetAddress(properties.getProperty(SERVER_BIND_ADDRESS, DEFAULT_BIND_ADDRESS)), valueOf(properties.getProperty(SERVER_PORT, DEFAULT_PORT)), - toServer(properties.getProperty(SERVER_CLASS, DEFAULT_CLASS))); + toServer(properties.getProperty(SERVER_CLASS, DEFAULT_CLASS)), + toSSLContext(properties.getProperty(SERVER_PROTOCOL, DEFAULT_PROTOCOL))); } public static ServerConfiguration defaultConfiguration() { @@ -66,7 +71,7 @@ public BasePath basePath() { } public ServerConfiguration basePath(BasePath basePath) { - return new ServerConfiguration(protocol, basePath, maxThreadNumber, bindAddress, port, serverClass); + return new ServerConfiguration(basePath, maxThreadNumber, bindAddress, port, serverClass, sslContext); } public int maxThreadNumber() { @@ -74,7 +79,7 @@ public int maxThreadNumber() { } public ServerConfiguration maxThreadNumber(int maxThreadNumber) { - return new ServerConfiguration(protocol, basePath, maxThreadNumber, bindAddress, port, serverClass); + return new ServerConfiguration(basePath, maxThreadNumber, bindAddress, port, serverClass, sslContext); } public Class serverClass() { @@ -82,7 +87,7 @@ public Class serverClass() { } public ServerConfiguration serverClass(Class serverClass) { - return new ServerConfiguration(protocol, basePath, maxThreadNumber, bindAddress, port, serverClass); + return new ServerConfiguration(basePath, maxThreadNumber, bindAddress, port, serverClass, sslContext); } public InetAddress bindAddress() { @@ -90,7 +95,7 @@ public InetAddress bindAddress() { } public ServerConfiguration bindAddress(InetAddress bindAddress) { - return new ServerConfiguration(protocol, basePath, maxThreadNumber, bindAddress, port, serverClass); + return new ServerConfiguration(basePath, maxThreadNumber, bindAddress, port, serverClass, sslContext); } public int port() { @@ -98,15 +103,28 @@ public int port() { } public ServerConfiguration port(int bindPort) { - return new ServerConfiguration(protocol, basePath, maxThreadNumber, bindAddress, bindPort, serverClass); + return new ServerConfiguration(basePath, maxThreadNumber, bindAddress, bindPort, serverClass, sslContext); } public String protocol() { - return protocol; + return sslContext.isDefined() ? Protocol.HTTPS : Protocol.HTTP; } public ServerConfiguration protocol(final String protocol) { - return new ServerConfiguration(protocol, basePath, maxThreadNumber, bindAddress, port, serverClass); + return new ServerConfiguration(basePath, maxThreadNumber, bindAddress, port, serverClass, toSSLContext(protocol)); + } + + public Option sslContext() { + return sslContext; + } + + public ServerConfiguration sslContext(final SSLContext sslContext) { + return new ServerConfiguration(basePath, maxThreadNumber, bindAddress, port, serverClass, some(sslContext)); + } + + private static Option toSSLContext(String protocol){ + if(protocol.equals(Protocol.HTTPS)) return some(SSL.sslContext()); + return none(); } private static InetAddress toInetAddress(final String address) { diff --git a/src/com/googlecode/utterlyidle/handlers/ClientHttpHandler.java b/src/com/googlecode/utterlyidle/handlers/ClientHttpHandler.java index a384c755..ceeaf615 100644 --- a/src/com/googlecode/utterlyidle/handlers/ClientHttpHandler.java +++ b/src/com/googlecode/utterlyidle/handlers/ClientHttpHandler.java @@ -25,6 +25,7 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import java.io.Closeable; import java.io.File; @@ -104,6 +105,10 @@ public ClientHttpHandler(int connectTimeoutMillis, int readTimeoutMillis, ProxyF this(connectTimeoutMillis, readTimeoutMillis, proxies, HttpsURLConnection.getDefaultHostnameVerifier(), HttpsURLConnection.getDefaultSSLSocketFactory()); } + public ClientHttpHandler(int connectTimeoutMillis, int readTimeoutMillis, ProxyFor proxies, HostnameVerifier hostnameVerifier, final SSLContext sslContext) { + this(connectTimeoutMillis, readTimeoutMillis, proxies, hostnameVerifier, sslContext.getSocketFactory()); + } + public ClientHttpHandler(int connectTimeoutMillis, int readTimeoutMillis, ProxyFor proxies, HostnameVerifier hostnameVerifier, final SSLSocketFactory sslSocketFactory) { this.connectTimeoutMillis = connectTimeoutMillis; this.readTimeoutMillis = readTimeoutMillis; diff --git a/src/com/googlecode/utterlyidle/httpserver/RestHandler.java b/src/com/googlecode/utterlyidle/httpserver/RestHandler.java index aad3a4d5..c2d9879b 100644 --- a/src/com/googlecode/utterlyidle/httpserver/RestHandler.java +++ b/src/com/googlecode/utterlyidle/httpserver/RestHandler.java @@ -4,6 +4,7 @@ import com.googlecode.totallylazy.Value; import com.googlecode.utterlyidle.Application; import com.googlecode.utterlyidle.HttpHeaders; +import com.googlecode.utterlyidle.Protocol; import com.googlecode.utterlyidle.Request; import com.googlecode.utterlyidle.Requests; import com.googlecode.utterlyidle.Response; @@ -11,6 +12,7 @@ import com.googlecode.utterlyidle.Status; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpsExchange; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -63,13 +65,10 @@ private Request request(HttpExchange httpExchange) { ); return requestEnricher( clientAddress(httpExchange.getRemoteAddress().getAddress()), - extractScheme(httpExchange.getProtocol())) + httpExchange instanceof HttpsExchange ? Protocol.HTTPS : Protocol.HTTP) .enrich(request); } - private String extractScheme(final String protocol) { - return protocol.toLowerCase().split("/")[0]; - } private Response exceptionResponse(Request request, final Exception e) throws IOException { System.err.println(String.format("%s %s -> %s", request.method(), request.uri(), e)); diff --git a/src/com/googlecode/utterlyidle/httpserver/RestServer.java b/src/com/googlecode/utterlyidle/httpserver/RestServer.java index 053633b5..872cbd73 100644 --- a/src/com/googlecode/utterlyidle/httpserver/RestServer.java +++ b/src/com/googlecode/utterlyidle/httpserver/RestServer.java @@ -3,11 +3,14 @@ import com.googlecode.totallylazy.Uri; import com.googlecode.utterlyidle.Application; import com.googlecode.utterlyidle.ApplicationBuilder; +import com.googlecode.utterlyidle.Protocol; import com.googlecode.utterlyidle.Server; import com.googlecode.utterlyidle.ServerConfiguration; import com.googlecode.utterlyidle.examples.HelloWorldApplication; import com.googlecode.utterlyidle.services.Service; import com.sun.net.httpserver.HttpServer; +import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.HttpsServer; import java.io.IOException; import java.net.InetSocketAddress; @@ -57,9 +60,8 @@ private HttpServer startApp(Application application, ServerConfiguration configu } private HttpServer startUpServer(Application application, ServerConfiguration configuration) throws Exception { - HttpServer server = HttpServer.create(new InetSocketAddress(configuration.bindAddress(), configuration.port()), 0); - server.createContext(configuration.basePath().toString(), - new RestHandler(application)); + HttpServer server = createServer(configuration); + server.createContext(configuration.basePath().toString(), new RestHandler(application)); executorService = newFixedThreadPool(configuration.maxThreadNumber(), getClass()); server.setExecutor(executorService); server.start(); @@ -67,4 +69,14 @@ private HttpServer startUpServer(Application application, ServerConfiguration co uri = updatedConfiguration.toUrl(); return server; } + + private HttpServer createServer(final ServerConfiguration configuration) throws IOException { + InetSocketAddress address = new InetSocketAddress(configuration.bindAddress(), configuration.port()); + if(configuration.protocol().equals(Protocol.HTTPS)) { + HttpsServer server = HttpsServer.create(address, 0); + server.setHttpsConfigurator(new HttpsConfigurator(configuration.sslContext().get())); + return server; + } + return HttpServer.create(address, 0); + } } diff --git a/src/com/googlecode/utterlyidle/jetty/RestServer.java b/src/com/googlecode/utterlyidle/jetty/RestServer.java index 5a508b1e..06e6d175 100644 --- a/src/com/googlecode/utterlyidle/jetty/RestServer.java +++ b/src/com/googlecode/utterlyidle/jetty/RestServer.java @@ -6,17 +6,21 @@ import com.googlecode.utterlyidle.Application; import com.googlecode.utterlyidle.ApplicationBuilder; import com.googlecode.utterlyidle.BasePath; +import com.googlecode.utterlyidle.Protocol; import com.googlecode.utterlyidle.ServerConfiguration; import com.googlecode.utterlyidle.examples.HelloWorldApplication; import com.googlecode.utterlyidle.services.Service; import com.googlecode.utterlyidle.servlet.ApplicationServlet; import com.googlecode.utterlyidle.servlet.ServletModule; +import org.mortbay.jetty.Connector; import org.mortbay.jetty.Server; import org.mortbay.jetty.nio.SelectChannelConnector; +import org.mortbay.jetty.security.SslSocketConnector; import org.mortbay.jetty.servlet.Context; import org.mortbay.jetty.webapp.WebAppContext; import org.mortbay.thread.QueuedThreadPool; +import javax.net.ssl.SSLServerSocketFactory; import javax.servlet.ServletContext; import java.io.IOException; @@ -117,14 +121,27 @@ public Context call(Server server) throws Exception { private Server createServer(ServerConfiguration serverConfig) { Server server = new Server(); - SelectChannelConnector selectChannelConnector = new SelectChannelConnector(); - selectChannelConnector.setPort(serverConfig.port()); - selectChannelConnector.setHost(serverConfig.bindAddress().getHostAddress()); + org.mortbay.jetty.Connector selectChannelConnector = connector(serverConfig); server.addConnector(selectChannelConnector); server.setThreadPool(new QueuedThreadPool(serverConfig.maxThreadNumber())); return server; } + private Connector connector(final ServerConfiguration serverConfig) { + if(serverConfig.protocol().equals(Protocol.HTTPS)){ + return new SslSocketConnector(){ + @Override + protected SSLServerSocketFactory createFactory() throws Exception { + return configuration.sslContext().get().getServerSocketFactory(); + } + }; + } + SelectChannelConnector selectChannelConnector = new SelectChannelConnector(); + selectChannelConnector.setPort(serverConfig.port()); + selectChannelConnector.setHost(serverConfig.bindAddress().getHostAddress()); + return selectChannelConnector; + } + private int portNumber(Server server) { return sequence(server.getConnectors()).head().getLocalPort(); } diff --git a/src/com/googlecode/utterlyidle/jetty/eclipse/RestServer.java b/src/com/googlecode/utterlyidle/jetty/eclipse/RestServer.java index b7843136..5216d4ca 100644 --- a/src/com/googlecode/utterlyidle/jetty/eclipse/RestServer.java +++ b/src/com/googlecode/utterlyidle/jetty/eclipse/RestServer.java @@ -1,5 +1,7 @@ package com.googlecode.utterlyidle.jetty.eclipse; +import com.googlecode.totallylazy.Callable1; +import com.googlecode.totallylazy.Option; import com.googlecode.totallylazy.Uri; import com.googlecode.utterlyidle.Application; import com.googlecode.utterlyidle.ApplicationBuilder; @@ -13,8 +15,10 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; +import javax.net.ssl.SSLContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -70,13 +74,24 @@ public void handle(String target, Request baseRequest, HttpServletRequest servle protected Server createServer(ServerConfiguration serverConfig) { Server server = new Server(new QueuedThreadPool(serverConfig.maxThreadNumber())); - ServerConnector serverConnector = new ServerConnector(server, new HttpConnectionFactory()); + ServerConnector serverConnector = new ServerConnector(server, sslContextFactory(serverConfig).getOrNull(), new HttpConnectionFactory()); serverConnector.setPort(serverConfig.port()); serverConnector.setHost(serverConfig.bindAddress().getHostAddress()); server.addConnector(serverConnector); return server; } + private Option sslContextFactory(final ServerConfiguration serverConfig) { + return serverConfig.sslContext().map(new Callable1() { + @Override + public SslContextFactory call(final SSLContext context) throws Exception { + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setSslContext(context); + return sslContextFactory; + } + }); + } + @Override public Application application() { return application; diff --git a/src/com/googlecode/utterlyidle/simpleframework/RestServer.java b/src/com/googlecode/utterlyidle/simpleframework/RestServer.java index f262841f..007c101a 100644 --- a/src/com/googlecode/utterlyidle/simpleframework/RestServer.java +++ b/src/com/googlecode/utterlyidle/simpleframework/RestServer.java @@ -58,7 +58,8 @@ private Connection startApp(Application application, ServerConfiguration configu private SocketConnection startUpApp(Application application, ServerConfiguration configuration) throws IOException { Container container = new RestContainer(application); SocketConnection connection = new SocketConnection(new ContainerSocketProcessor(container, configuration.maxThreadNumber())); - InetSocketAddress socketAddress = (InetSocketAddress) connection.connect(new InetSocketAddress(configuration.bindAddress(), configuration.port())); + InetSocketAddress socketAddress = (InetSocketAddress) connection.connect(new InetSocketAddress(configuration.bindAddress(), configuration.port()), + configuration.sslContext().getOrNull()); ServerConfiguration updatedConfiguration = configuration.port(socketAddress.getPort()); uri = updatedConfiguration.toUrl(); diff --git a/src/com/googlecode/utterlyidle/ssl/SSL.java b/src/com/googlecode/utterlyidle/ssl/SSL.java new file mode 100644 index 00000000..1a71c2ad --- /dev/null +++ b/src/com/googlecode/utterlyidle/ssl/SSL.java @@ -0,0 +1,108 @@ +package com.googlecode.utterlyidle.ssl; + +import com.googlecode.totallylazy.Maps; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.util.Map; + +import static com.googlecode.totallylazy.LazyException.lazyException; + +public class SSL { + public static final String DEFAULT_SSL_CONTEXT = "TLSv1.2"; + private static final Map storeType = Maps.map( + "jks", "JKS", + "p12", "PKCS12"); + + public static KeyStore keyStore(SecureString password, File file) { + try { + try (InputStream inputStream = new FileInputStream(file)) { + return keyStore(password, inputStream, storeType.get(extension(file))); + } + } catch (Exception e) { + throw lazyException(e); + } + } + + public static KeyStore keyStore(final SecureString password, final InputStream inputStream) { + return keyStore(password, inputStream, "JKS"); + } + + public static KeyStore keyStore(final SecureString password, final InputStream inputStream, final String type) { + try { + KeyStore keyStore = KeyStore.getInstance(type); + keyStore.load(inputStream, password.characters()); + return keyStore; + } catch (Exception e) { + throw lazyException(e); + } + } + + private static String extension(File file) { + String[] parts = file.getName().split("\\."); + return parts[parts.length - 1]; + } + + public static SSLContext sslContext(KeyStore keyStore) { + return sslContext(keyStore, DEFAULT_SSL_CONTEXT); + } + + public static SSLContext sslContext(final KeyStore keyStore, final SecureString privateKeyPassword) { + return sslContext(keyStore, DEFAULT_SSL_CONTEXT, privateKeyPassword); + } + + public static SSLContext sslContext(final KeyStore keyStore, final String name, final SecureString privateKeyPassword) { + return sslContext(name, keyManagers(keyStore, privateKeyPassword), trustManagers(keyStore)); + } + + public static SSLContext sslContext(final KeyStore keyStore, final String name) { + return sslContext(name, null, trustManagers(keyStore)); + } + + public static SSLContext sslContext(final String name, final KeyManager[] keyManagers, final TrustManager[] trustManagers) { + try { + SSLContext context = SSLContext.getInstance(name); + context.init(keyManagers, trustManagers, null); + return context; + } catch (Exception e) { + throw lazyException(e); + } + } + + public static KeyManager[] keyManagers(final KeyStore keyStore, final SecureString secureString) { + try { + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, secureString.characters()); + return keyManagerFactory.getKeyManagers(); + } catch (Exception e) { + throw lazyException(e); + } + } + + public static TrustManager[] trustManagers(final KeyStore keyStore) { + try { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + return trustManagerFactory.getTrustManagers(); + } catch (Exception e) { + throw lazyException(e); + } + } + + public static SSLContext sslContext() { + try { + return SSLContext.getDefault(); + } catch (Exception e) { + throw lazyException(e); + } + } +} diff --git a/src/com/googlecode/utterlyidle/ssl/SecureString.java b/src/com/googlecode/utterlyidle/ssl/SecureString.java new file mode 100644 index 00000000..5f2ef2f1 --- /dev/null +++ b/src/com/googlecode/utterlyidle/ssl/SecureString.java @@ -0,0 +1,45 @@ +package com.googlecode.utterlyidle.ssl; + +import java.util.Arrays; + +public class SecureString implements AutoCloseable, CharSequence { + private final char[] value; + + protected SecureString(final char[] value) { + this.value = value; + } + + public static SecureString secureString(final char... value) { + return new SecureString(value); + } + + @Override + public void close() throws Exception { + int length = value.length; + for (int i = 0; i < length; i++) value[i] = 0; + } + + public char[] characters() { + return value; + } + + @Override + public int length() { + return value.length; + } + + @Override + public char charAt(final int index) { + return value[index]; + } + + @Override + public SecureString subSequence(final int start, final int end) { + return secureString(Arrays.copyOfRange(value, start, end)); + } + + @Override + public String toString() { + return "***********"; + } +} diff --git a/test/com/googlecode/utterlyidle/ServerContract.java b/test/com/googlecode/utterlyidle/ServerContract.java index 54c96c75..4bfa8050 100644 --- a/test/com/googlecode/utterlyidle/ServerContract.java +++ b/test/com/googlecode/utterlyidle/ServerContract.java @@ -1,20 +1,25 @@ package com.googlecode.utterlyidle; -import com.googlecode.totallylazy.Option; import com.googlecode.totallylazy.Predicates; import com.googlecode.totallylazy.Sequence; import com.googlecode.totallylazy.time.Dates; import com.googlecode.utterlyidle.examples.HelloWorldApplication; +import com.googlecode.utterlyidle.handlers.ClientHttpHandler; import com.googlecode.utterlyidle.rendering.exceptions.LastExceptions; import com.googlecode.utterlyidle.rendering.exceptions.StoredException; +import com.googlecode.utterlyidle.ssl.SecureString; +import com.googlecode.utterlyidle.ssl.SecureStringTest; import org.hamcrest.CoreMatchers; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Test; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import java.io.InputStream; + import static com.googlecode.totallylazy.Option.none; -import static com.googlecode.totallylazy.Option.some; import static com.googlecode.totallylazy.Predicates.not; import static com.googlecode.totallylazy.Sequences.sequence; import static com.googlecode.totallylazy.Unchecked.cast; @@ -39,6 +44,9 @@ import static com.googlecode.utterlyidle.ServerConfiguration.defaultConfiguration; import static com.googlecode.utterlyidle.Status.NOT_FOUND; import static com.googlecode.utterlyidle.handlers.ClientHttpHandlerTest.handle; +import static com.googlecode.utterlyidle.ssl.SSL.keyStore; +import static com.googlecode.utterlyidle.ssl.SSL.sslContext; +import static com.googlecode.utterlyidle.ssl.SecureString.secureString; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -52,7 +60,11 @@ public abstract class ServerContract { @Before public void start() throws Exception { - server = cast(application(HelloWorldApplication.class).start(defaultConfiguration().basePath(basePath("base/path")).serverClass(server()))); + server = configureServer(defaultConfiguration()); + } + + private T configureServer(final ServerConfiguration configuration) throws Exception { + return cast(application(HelloWorldApplication.class).start(configuration.basePath(basePath("base/path")).serverClass(server()))); } @After @@ -219,4 +231,20 @@ public void canHandleOptionalDate() throws Exception { assertThat(handle(get("optionalDate?date="), server).entity().toString(), is("no date")); assertThat(handle(get("optionalDate?date=" + LEXICAL().format(Dates.date(1974, 10, 29))), server).entity().toString(), is("19741029000000000")); } + + @Test + public void supportsHttps() throws Exception { + try(InputStream resource = SecureStringTest.class.getResourceAsStream("localhost.jks"); + SecureString password = secureString('p', 'a', 's', 's', 'w', 'o', 'r', 'd')) { + SSLContext sslContext = sslContext(keyStore(password, resource), password); + server.close(); + server = configureServer(defaultConfiguration().sslContext(sslContext)); + Response response = handle(new ClientHttpHandler(1000, 1000, ClientHttpHandler.DEFAULT_PROXY, HttpsURLConnection.getDefaultHostnameVerifier(), sslContext), get("helloworld/x-forwarded-proto"), server); + + assertThat(response.status(), is(Status.OK)); + assertThat(response.entity().toString(), is(Protocol.HTTPS)); + } + + } + } \ No newline at end of file diff --git a/test/com/googlecode/utterlyidle/handlers/ClientHttpHandlerTest.java b/test/com/googlecode/utterlyidle/handlers/ClientHttpHandlerTest.java index 69b376a4..55b33972 100644 --- a/test/com/googlecode/utterlyidle/handlers/ClientHttpHandlerTest.java +++ b/test/com/googlecode/utterlyidle/handlers/ClientHttpHandlerTest.java @@ -191,7 +191,7 @@ public void canPostToAResource() throws Exception { } public static Response handle(final RequestBuilder request, final Server server) throws Exception { - return handle(new ClientHttpHandler(0), request, server); + return handle(0, request, server); } public static Response handle(int timeout, final RequestBuilder request, final Server server) throws Exception { diff --git a/test/com/googlecode/utterlyidle/ssl/SecureStringTest.java b/test/com/googlecode/utterlyidle/ssl/SecureStringTest.java new file mode 100644 index 00000000..88b60750 --- /dev/null +++ b/test/com/googlecode/utterlyidle/ssl/SecureStringTest.java @@ -0,0 +1,18 @@ +package com.googlecode.utterlyidle.ssl; + +import org.junit.Test; + +import static com.googlecode.utterlyidle.ssl.SecureString.secureString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SecureStringTest { + @Test + public void supportsSecureWipingOfValue() throws Exception { + char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}; + try(SecureString secureString = secureString(password)) { + for (int i = 0; i < password.length; i++) assertThat(secureString.charAt(i), is(password[i])); + } + for (final char c : password) assertThat(c, is((char) 0)); + } +} \ No newline at end of file diff --git a/test/com/googlecode/utterlyidle/ssl/generate.keystore.sh b/test/com/googlecode/utterlyidle/ssl/generate.keystore.sh new file mode 100644 index 00000000..1a1f49c7 --- /dev/null +++ b/test/com/googlecode/utterlyidle/ssl/generate.keystore.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +keytool -genkey -noprompt -trustcacerts -alias localhost -dname "cn=localhost" -validity 10000 -keypass password -keystore localhost.jks -storepass password \ No newline at end of file diff --git a/test/com/googlecode/utterlyidle/ssl/localhost.jks b/test/com/googlecode/utterlyidle/ssl/localhost.jks new file mode 100644 index 0000000000000000000000000000000000000000..7a1e2708de693c045439a6d37c69e2c78a147fad GIT binary patch literal 1121 zcmezO_TO6u1_mY|W&~r-oc!d(oQ(Y95}-)H+!K2y0_lE(CdO_9J~l3GHbxdkEha%m zMpg!vCdTsAWTV$}{nv%9{`WX3Q}4{d*|(ilY|C?h+MMHH>-pJuYWYPC#@;|Hp=I5Y zd3;BoZE-4n%>R3*3gfX#fw|`&M9fW9c@tz>wBlDrMQ3^RmAXsC6Qb(h9k*VW8=ui* zmTCP%VrTkQrkKTTTHbjRRDZ@5PyVNJowF*{Aa{+2#w_DAr$5YgQ=ZwDnS5(%LdRjT z3%l38)qm8ia;8{yzQ!sA8~cYhPH9$LQNFOd%*&{puSmk@*)-irv0EkP$E%H-DamIcX;BA|3^f6m)y*f{bD0$C>7~-SpMI8*^;wmUrUZ~t$xLH z<@V(NlWhXuJ=C14Snp;k*?A_gPSjd_wwKJyD{%>a_jb*C%yV>~f>T9VgXjKfh99=p zOe}vKu=DQUk4-*Fi(VLXC`AVrah4KR>o&nJ7QY@8)lpkIEfpG7S-q5}RoBt@QIc z?ZoR^?>{LqOU}LM!FZfWlwo>8K)Bg6Z4U5WI6R>&^U*s#S@tWf!dkA|4=_Vb)Z zJtvkuv=OW74yozYc(8b#oc88rr4#cO*K_|;G)?;WJE@9M#rn*WkNZ{xPUu|GeE(t8 z)A?+Zy3^tsszX&%7=5h&o%hsYZfs#-YHZ+q7Ufzj?C~WgAmq}kkK3I#vR64Qym7yF z$KU7GtCAky+gp9o(RTeJ?e}kQ?Yon5a$!weaB=qE&Fw5_=U<(_K5WCRC!w3VrmL-a z@ak29P+9RxadE{~3HG2JTUt(@HtCCeAQbJl^sn)i4II0Q?I&mLROfkFJFm|`HEXr| zH=eLvJj)g<8ps>S0+WF(ABz}^$jbd(nL9$oL*8wzAo4YNLjfew?1 z(za#JMzeeuecquNJagy1Szhb)l9)s|{{?3jtvaPFL(cuhyV- RlA-5Il73x%+(r3=c>q=%;XVKW literal 0 HcmV?d00001