Skip to content

Commit

Permalink
support https/tls in server module
Browse files Browse the repository at this point in the history
  • Loading branch information
stevehu committed Feb 3, 2017
1 parent 75d56a8 commit b4ebfb3
Show file tree
Hide file tree
Showing 17 changed files with 860 additions and 84 deletions.
17 changes: 17 additions & 0 deletions docs/content/other/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,20 @@ Here is an exmaple of server.json
}
```

## TLS Hostname Verification

For testing, we can disable the hostname verification on the client for the certificate;
however, it is recommended that on production, hostname verification should be turned on
to eliminate man-in-the-middle attacks.

You have two options:

* Buy certificates from a CA like VeriSign.
* Setup a CA in your organization and use openssl to generate certificate.

For more information

http://stackoverflow.com/questions/29546834/trust-not-trusted-certificates-and-skip-hostname-verification/29547114#29547114
https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning

34 changes: 34 additions & 0 deletions docs/content/tools/keytool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
date: 2017-02-03T14:12:47-05:00
title: keytool
---

This is a Java command line tool to generate and manipulate keys.

To create a server.keystore for TLS.

```
keytool -genkey -alias mycert -keyalg RSA -sigalg MD5withRSA -keystore server.keystore -storepass secret -keypass secret -validity 9999
```
And then copy this file to light-java/src/main/resources/config/tls folder. At
the same time, update server.json for keystoreName, keystorePass, keyPass.

```
{
"description": "server config",
"ip": "0.0.0.0",
"httpPort": 8080,
"enableHttp": true,
"httpsPort": 8443,
"enableHttps": true,
"keystoreName": "tls/server.keystore",
"keystorePass": "secret",
"keyPass": "secret",
"enableTwoWayTls": false,
"truststoreName": "tls/server.truststore",
"truststorePass": "password",
"serviceId": "com.networknt.petstore-1.0.0",
"enableRegistry": false
}
```
20 changes: 20 additions & 0 deletions server/src/main/java/com/networknt/server/DummyTrustManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.networknt.server;

import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;

/**
* Created by steve on 03/02/17.
*/
public class DummyTrustManager implements X509TrustManager {

public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[] {};
}

public void checkClientTrusted(X509Certificate[] certs, String authType) {
}

public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
148 changes: 135 additions & 13 deletions server/src/main/java/com/networknt/server/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,16 @@
import org.slf4j.LoggerFactory;
import org.xnio.Options;

import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.util.ServiceLoader;


Expand All @@ -44,11 +53,16 @@ public class Server {
static final String CONFIG_NAME = "server";
public static ServerConfig config = (ServerConfig) Config.getInstance().getJsonObjectConfig(CONFIG_NAME, ServerConfig.class);

public final static TrustManager[] TRUST_ALL_CERTS = new X509TrustManager[] { new DummyTrustManager() };

static protected boolean shutdownRequested = false;
static Undertow server = null;
static URL serviceUrl;
static URL serviceHttpUrl;
static URL serviceHttpsUrl;
static Registry registry;

static SSLContext sslContext;

public static void main(final String[] args) {
logger.info("server starts");
start();
Expand All @@ -65,15 +79,23 @@ static public void start() {
provider.onStartup();
}

// application level service registry. only be used without docker container.
if(config.enableRegistry) {
// assuming that registry is defined in service.json, otherwise won't start server.
registry = (Registry) SingletonServiceFactory.getBean(Registry.class);
if(registry == null) throw new RuntimeException("Could not find registry instance in service map");
InetAddress inetAddress = Util.getInetAddress();
String ipAddress = inetAddress.getHostAddress();
serviceUrl = new URLImpl("light", ipAddress, config.getPort(), config.getServiceId());
registry.register(serviceUrl);
if(logger.isInfoEnabled()) logger.info("register serviceUrl " + serviceUrl);
if(config.enableHttp) {
serviceHttpUrl = new URLImpl("light", ipAddress, config.getHttpPort(), config.getServiceId());
registry.register(serviceHttpUrl);
if(logger.isInfoEnabled()) logger.info("register serviceHttpUrl " + serviceHttpUrl);
}
if(config.enableHttps) {
serviceHttpsUrl = new URLImpl("light", ipAddress, config.getHttpsPort(), config.getServiceId());
registry.register(serviceHttpsUrl);
if(logger.isInfoEnabled()) logger.info("register serviceHttpsUrl " + serviceHttpsUrl);
}
}

HttpHandler handler = null;
Expand All @@ -87,7 +109,7 @@ static public void start() {
}
}
if (handler == null) {
logger.warn("No route handler provider available in the classpath");
logger.error("Unable to start the server - no route handler provider available in the classpath");
return;
}

Expand All @@ -102,10 +124,17 @@ static public void start() {
}
}

server = Undertow.builder()
.addHttpListener(
config.getPort(),
config.getIp())
Undertow.Builder builder = Undertow.builder();

if(config.enableHttp) {
builder.addHttpListener(config.getHttpPort(), config.getIp());
}
if(config.enableHttps) {
sslContext = createSSLContext();
builder.addHttpsListener(config.getHttpsPort(), config.getIp(), sslContext);
}

server = builder
.setBufferSize(1024 * 16)
.setIoThreads(Runtime.getRuntime().availableProcessors() * 2) //this seems slightly faster in some configurations
.setSocketOption(Options.BACKLOG, 10000)
Expand All @@ -117,7 +146,16 @@ static public void start() {
.setWorkerThreads(200)
.build();
server.start();
if(logger.isInfoEnabled()) logger.info("Server started on IP:" + config.getIp() + " Port:" + config.getPort());

if(logger.isInfoEnabled()) {
if(config.enableHttp) {
logger.info("Http Server started on ip:" + config.getIp() + " Port:" + config.getHttpPort());
}
if(config.enableHttps) {
logger.info("Https Server started on ip:" + config.getIp() + " Port:" + config.getHttpsPort());
}
}

if(config.enableRegistry) {
// start heart beat if registry is enabled
SwitcherUtil.setSwitcherValue(Constants.REGISTRY_HEARTBEAT_SWITCHER, true);
Expand All @@ -133,9 +171,13 @@ static public void stop() {
static public void shutdown() {

// need to unregister the service
if(config.enableRegistry && registry != null) {
registry.unregister(serviceUrl);
if(logger.isInfoEnabled()) logger.info("unregister serviceUrl " + serviceUrl);
if(config.enableRegistry && registry != null && config.enableHttp) {
registry.unregister(serviceHttpUrl);
if(logger.isInfoEnabled()) logger.info("unregister serviceHttpUrl " + serviceHttpUrl);
}
if(config.enableRegistry && registry != null && config.enableHttps) {
registry.unregister(serviceHttpsUrl);
if(logger.isInfoEnabled()) logger.info("unregister serviceHttpsUrl " + serviceHttpsUrl);
}

final ServiceLoader<ShutdownHookProvider> shutdownLoaders = ServiceLoader.load(ShutdownHookProvider.class);
Expand All @@ -154,4 +196,84 @@ public void run() {
}
});
}

private static KeyStore loadKeyStore() {
String name = config.getKeystoreName();
try (InputStream stream = Config.getInstance().getInputStreamFromFile(name)) {
KeyStore loadedKeystore = KeyStore.getInstance("JKS");
loadedKeystore.load(stream, config.getKeystorePass().toCharArray());
return loadedKeystore;
} catch (Exception e) {
logger.error("Unable to load keystore " + name, e);
throw new RuntimeException("Unable to load keystore " + name, e);
}
}

protected static KeyStore loadTrustStore() {
String name = config.getTruststoreName();
try (InputStream stream = Config.getInstance().getInputStreamFromFile(name)) {
KeyStore loadedKeystore = KeyStore.getInstance("JKS");
loadedKeystore.load(stream, config.getTruststorePass().toCharArray());
return loadedKeystore;
} catch (Exception e) {
logger.error("Unable to load truststore " + name, e);
throw new RuntimeException("Unable to load truststore " + name, e);
}
}

private static TrustManager[] buildTrustManagers(final KeyStore trustStore) {
TrustManager[] trustManagers = null;
if (trustStore == null) {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
trustManagers = trustManagerFactory.getTrustManagers();
}
catch (NoSuchAlgorithmException | KeyStoreException e) {
logger.error("Unable to initialise TrustManager[]", e);
throw new RuntimeException("Unable to initialise TrustManager[]", e);
}
}
else {
trustManagers = TRUST_ALL_CERTS;
}
return trustManagers;
}

private static KeyManager[] buildKeyManagers(final KeyStore keyStore, char[] keyPass) {
KeyManager[] keyManagers;
try {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory
.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyPass);
keyManagers = keyManagerFactory.getKeyManagers();
}
catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
logger.error("Unable to initialise KeyManager[]", e);
throw new RuntimeException("Unable to initialise KeyManager[]", e);
}
return keyManagers;
}

private static SSLContext createSSLContext() throws RuntimeException {
try {
KeyManager[] keyManagers = buildKeyManagers(loadKeyStore(), config.getKeyPass().toCharArray());
TrustManager[] trustManagers;
if(config.isEnableTwoWayTls()) {
trustManagers = buildTrustManagers(loadTrustStore());
} else {
trustManagers = buildTrustManagers(null);
}

SSLContext sslContext;
sslContext = SSLContext.getInstance("TLSv1");
sslContext.init(keyManagers, trustManagers, null);
return sslContext;
} catch (Exception e) {
logger.error("Unable to create SSLContext", e);
throw new RuntimeException("Unable to create SSLContext", e);
}
}

}
91 changes: 86 additions & 5 deletions server/src/main/java/com/networknt/server/ServerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,16 @@

public class ServerConfig {
String ip;
int port;
int httpPort;
boolean enableHttp;
int httpsPort;
boolean enableHttps;
String keystoreName;
String keystorePass;
String keyPass;
boolean enableTwoWayTls;
String truststoreName;
String truststorePass;
boolean enableRegistry;
String serviceId;

Expand All @@ -38,12 +47,84 @@ public void setIp(String ip) {
this.ip = ip;
}

public int getPort() {
return port;
public int getHttpPort() {
return httpPort;
}

public void setPort(int port) {
this.port = port;
public void setHttpPort(int httpPort) {
this.httpPort = httpPort;
}

public boolean isEnableHttp() {
return enableHttp;
}

public void setEnableHttp(boolean enableHttp) {
this.enableHttp = enableHttp;
}

public int getHttpsPort() {
return httpsPort;
}

public void setHttpsPort(int httpsPort) {
this.httpsPort = httpsPort;
}

public boolean isEnableHttps() {
return enableHttps;
}

public void setEnableHttps(boolean enableHttps) {
this.enableHttps = enableHttps;
}

public String getKeystoreName() {
return keystoreName;
}

public void setKeystoreName(String keystoreName) {
this.keystoreName = keystoreName;
}

public String getTruststoreName() {
return truststoreName;
}

public void setTruststoreName(String truststoreName) {
this.truststoreName = truststoreName;
}

public String getKeystorePass() {
return keystorePass;
}

public void setKeystorePass(String keystorePass) {
this.keystorePass = keystorePass;
}

public String getKeyPass() {
return keyPass;
}

public void setKeyPass(String keyPass) {
this.keyPass = keyPass;
}

public boolean isEnableTwoWayTls() {
return enableTwoWayTls;
}

public void setEnableTwoWayTls(boolean enableTwoWayTls) {
this.enableTwoWayTls = enableTwoWayTls;
}

public String getTruststorePass() {
return truststorePass;
}

public void setTruststorePass(String truststorePass) {
this.truststorePass = truststorePass;
}

public boolean isEnableRegistry() {
Expand Down
Loading

0 comments on commit b4ebfb3

Please sign in to comment.