support ssl

master
fengfei 7 years ago
parent 3de9f5946b
commit e51f9c5010
  1. 2
      proxy-client/pom.xml
  2. 20
      proxy-client/src/main/java/org/fengfei/lanproxy/client/ProxyClientContainer.java
  3. 90
      proxy-client/src/main/java/org/fengfei/lanproxy/client/SslContextCreator.java
  4. 5
      proxy-client/src/main/resources/config.properties
  5. BIN
      proxy-client/src/main/resources/test.jks
  6. 7
      proxy-client/src/test/resources/config.properties
  7. BIN
      proxy-client/src/test/resources/test.jks
  8. 2
      proxy-server/pom.xml
  9. 58
      proxy-server/src/main/java/org/fengfei/lanproxy/server/ProxyServerContainer.java
  10. 115
      proxy-server/src/main/java/org/fengfei/lanproxy/server/SslContextCreator.java
  11. 44
      proxy-server/src/main/java/org/fengfei/lanproxy/server/handlers/ServerChannelHandler.java
  12. 8
      proxy-server/src/main/resources/config.properties
  13. BIN
      proxy-server/src/main/resources/test.jks
  14. 10
      proxy-server/src/test/resources/config.properties
  15. BIN
      proxy-server/src/test/resources/test.jks

@ -54,6 +54,7 @@
<exclude>*.properties</exclude>
<exclude>*.sh</exclude>
<exclude>*.bat</exclude>
<exclude>*.jks</exclude>
</excludes>
</configuration>
</plugin>
@ -76,6 +77,7 @@
<directory>src/main/resources</directory>
<includes>
<include>*.properties</include>
<include>*.jks</include>
</includes>
</resource>
</resources>

@ -2,6 +2,9 @@ package org.fengfei.lanproxy.client;
import java.util.Arrays;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import org.fengfei.lanproxy.client.handlers.ClientChannelHandler;
import org.fengfei.lanproxy.client.handlers.RealServerChannelHandler;
import org.fengfei.lanproxy.client.listener.ChannelStatusListener;
@ -18,11 +21,13 @@ import org.slf4j.LoggerFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslHandler;
public class ProxyClientContainer implements Container, ChannelStatusListener {
@ -46,6 +51,8 @@ public class ProxyClientContainer implements Container, ChannelStatusListener {
private Config config = Config.getInstance();
private SSLContext sslContext;
public ProxyClientContainer() {
workerGroup = new NioEventLoopGroup();
realServerBootstrap = new Bootstrap();
@ -66,6 +73,13 @@ public class ProxyClientContainer implements Container, ChannelStatusListener {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (Config.getInstance().getBooleanValue("ssl.enable", false)) {
if (sslContext == null) {
sslContext = SslContextCreator.createSSLContext();
}
ch.pipeline().addLast(createSslHandler(sslContext));
}
ch.pipeline().addLast(new ProxyMessageDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET,
LENGTH_FIELD_LENGTH, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP));
ch.pipeline().addLast(new ProxyMessageEncoder());
@ -81,6 +95,12 @@ public class ProxyClientContainer implements Container, ChannelStatusListener {
connectProxyServer();
}
private ChannelHandler createSslHandler(SSLContext sslContext) {
SSLEngine sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(true);
return new SslHandler(sslEngine);
}
private void connectProxyServer() {
bootstrap.connect(config.getStringValue("server.host"), config.getIntValue("server.port"))

@ -0,0 +1,90 @@
package org.fengfei.lanproxy.client;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.fengfei.lanproxy.common.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SslContextCreator {
private static Logger logger = LoggerFactory.getLogger(SslContextCreator.class);
public static SSLContext createSSLContext() {
return new SslContextCreator().initSSLContext();
}
public SSLContext initSSLContext() {
logger.info("Checking SSL configuration properties...");
final String jksPath = Config.getInstance().getStringValue("ssl.jksPath");
logger.info("Initializing SSL context. KeystorePath = {}.", jksPath);
if (jksPath == null || jksPath.isEmpty()) {
// key_store_password or key_manager_password are empty
logger.warn("The keystore path is null or empty. The SSL context won't be initialized.");
return null;
}
// if we have the port also the jks then keyStorePassword and
// keyManagerPassword
// has to be defined
final String keyStorePassword = Config.getInstance().getStringValue("ssl.keyStorePassword");
// if client authentification is enabled a trustmanager needs to be
// added to the ServerContext
try {
logger.info("Loading keystore. KeystorePath = {}.", jksPath);
InputStream jksInputStream = jksDatastore(jksPath);
SSLContext clientSSLContext = SSLContext.getInstance("TLS");
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(jksInputStream, keyStorePassword.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
TrustManager[] trustManagers = tmf.getTrustManagers();
// init sslContext
logger.info("Initializing SSL context...");
clientSSLContext.init(null, trustManagers, null);
logger.info("The SSL context has been initialized successfully.");
return clientSSLContext;
} catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | KeyManagementException
| IOException ex) {
logger.error("Unable to initialize SSL context. Cause = {}, errorMessage = {}.", ex.getCause(),
ex.getMessage());
return null;
}
}
private InputStream jksDatastore(String jksPath) throws FileNotFoundException {
URL jksUrl = getClass().getClassLoader().getResource(jksPath);
if (jksUrl != null) {
logger.info("Starting with jks at {}, jks normal {}", jksUrl.toExternalForm(), jksUrl);
return getClass().getClassLoader().getResourceAsStream(jksPath);
}
logger.warn("No keystore has been found in the bundled resources. Scanning filesystem...");
File jksFile = new File(jksPath);
if (jksFile.exists()) {
logger.info("Loading external keystore. Url = {}.", jksFile.getAbsolutePath());
return new FileInputStream(jksFile);
}
logger.warn("The keystore file does not exist. Url = {}.", jksFile.getAbsolutePath());
return null;
}
}

@ -1,4 +1,9 @@
client.key=client
ssl.enable=false
ssl.jksPath=test.jks
ssl.keyStorePassword=123456
server.host=127.0.0.1
#default ssl port is 8883
server.port=4900

@ -1,4 +1,9 @@
client.key=client
ssl.enable=true
ssl.jksPath=test.jks
ssl.keyStorePassword=123456
server.host=127.0.0.1
server.port=4900
#default ssl port is 8883, none ssl port is 4900
server.port=8883

@ -34,6 +34,7 @@
<exclude>*.properties</exclude>
<exclude>*.sh</exclude>
<exclude>*.bat</exclude>
<exclude>*.jks</exclude>
</excludes>
</configuration>
</plugin>
@ -77,6 +78,7 @@
<includes>
<include>*.properties</include>
<include>*.json</include>
<include>*.jks</include>
</includes>
</resource>
</resources>

@ -4,6 +4,10 @@ import java.net.BindException;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import org.fengfei.lanproxy.common.Config;
import org.fengfei.lanproxy.common.container.Container;
import org.fengfei.lanproxy.common.container.ContainerHelper;
import org.fengfei.lanproxy.protocol.IdleCheckHandler;
@ -18,10 +22,14 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslHandler;
public class ProxyServerContainer implements Container, ConfigChangedListener {
@ -76,10 +84,50 @@ public class ProxyServerContainer implements Container, ConfigChangedListener {
throw new RuntimeException(ex);
}
if (Config.getInstance().getBooleanValue("server.ssl.enable", false)) {
String host = Config.getInstance().getStringValue("server.ssl.bind", "0.0.0.0");
int port = Config.getInstance().getIntValue("server.ssl.port");
initializeSSLTCPTransport(host, port, new SslContextCreator().initSSLContext());
}
startUserPort();
}
private void initializeSSLTCPTransport(String host, int port, final SSLContext sslContext) {
ServerBootstrap b = new ServerBootstrap();
b.group(serverBossGroup, serverWorkerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
try {
pipeline.addLast("ssl", createSslHandler(sslContext,
Config.getInstance().getBooleanValue("server.ssl.needsClientAuth", false)));
ch.pipeline().addLast(new ProxyMessageDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET,
LENGTH_FIELD_LENGTH, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP));
ch.pipeline().addLast(new ProxyMessageEncoder());
ch.pipeline().addLast(new IdleCheckHandler(IdleCheckHandler.READ_IDLE_TIME,
IdleCheckHandler.WRITE_IDLE_TIME, 0));
ch.pipeline().addLast(new ServerChannelHandler());
} catch (Throwable th) {
logger.error("Severe error during pipeline creation", th);
throw th;
}
}
});
try {
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(host, port);
f.sync();
logger.info("proxy ssl server start on port {}", port);
} catch (InterruptedException ex) {
logger.error("An interruptedException was caught while initializing server", ex);
}
}
private void startUserPort() {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(serverBossGroup, serverWorkerGroup).channel(NioServerSocketChannel.class)
@ -119,6 +167,16 @@ public class ProxyServerContainer implements Container, ConfigChangedListener {
serverWorkerGroup.shutdownGracefully();
}
private ChannelHandler createSslHandler(SSLContext sslContext, boolean needsClientAuth) {
SSLEngine sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(false);
if (needsClientAuth) {
sslEngine.setNeedClientAuth(true);
}
return new SslHandler(sslEngine);
}
public static void main(String[] args) {
ContainerHelper.start(Arrays.asList(new Container[] { new ProxyServerContainer(), new WebConfigContainer() }));
}

@ -0,0 +1,115 @@
package org.fengfei.lanproxy.server;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
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.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.fengfei.lanproxy.common.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SslContextCreator {
private static Logger logger = LoggerFactory.getLogger(SslContextCreator.class);
public SSLContext initSSLContext() {
logger.info("Checking SSL configuration properties...");
final String jksPath = Config.getInstance().getStringValue("server.ssl.jksPath");
logger.info("Initializing SSL context. KeystorePath = {}.", jksPath);
if (jksPath == null || jksPath.isEmpty()) {
// key_store_password or key_manager_password are empty
logger.warn("The keystore path is null or empty. The SSL context won't be initialized.");
return null;
}
// if we have the port also the jks then keyStorePassword and
// keyManagerPassword
// has to be defined
final String keyStorePassword = Config.getInstance().getStringValue("server.ssl.keyStorePassword");
final String keyManagerPassword = Config.getInstance().getStringValue("server.ssl.keyManagerPassword");
if (keyStorePassword == null || keyStorePassword.isEmpty()) {
// key_store_password or key_manager_password are empty
logger.warn("The keystore password is null or empty. The SSL context won't be initialized.");
return null;
}
if (keyManagerPassword == null || keyManagerPassword.isEmpty()) {
// key_manager_password or key_manager_password are empty
logger.warn("The key manager password is null or empty. The SSL context won't be initialized.");
return null;
}
// if client authentification is enabled a trustmanager needs to be
// added to the ServerContext
boolean needsClientAuth = Config.getInstance().getBooleanValue("server.ssl.needsClientAuth", false);
try {
logger.info("Loading keystore. KeystorePath = {}.", jksPath);
InputStream jksInputStream = jksDatastore(jksPath);
SSLContext serverContext = SSLContext.getInstance("TLS");
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(jksInputStream, keyStorePassword.toCharArray());
logger.info("Initializing key manager...");
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyManagerPassword.toCharArray());
TrustManager[] trustManagers = null;
if (needsClientAuth) {
logger.warn(
"Client authentication is enabled. The keystore will be used as a truststore. KeystorePath = {}.",
jksPath);
// use keystore as truststore, as server needs to trust
// certificates signed by the
// server certificates
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
trustManagers = tmf.getTrustManagers();
}
// init sslContext
logger.info("Initializing SSL context...");
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
logger.info("The SSL context has been initialized successfully.");
return serverContext;
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | KeyStoreException
| KeyManagementException | IOException ex) {
logger.error("Unable to initialize SSL context. Cause = {}, errorMessage = {}.", ex.getCause(),
ex.getMessage());
return null;
}
}
private InputStream jksDatastore(String jksPath) throws FileNotFoundException {
URL jksUrl = getClass().getClassLoader().getResource(jksPath);
if (jksUrl != null) {
logger.info("Starting with jks at {}, jks normal {}", jksUrl.toExternalForm(), jksUrl);
return getClass().getClassLoader().getResourceAsStream(jksPath);
}
logger.warn("No keystore has been found in the bundled resources. Scanning filesystem...");
File jksFile = new File(jksPath);
if (jksFile.exists()) {
logger.info("Loading external keystore. Url = {}.", jksFile.getAbsolutePath());
return new FileInputStream(jksFile);
}
logger.warn("The keystore file does not exist. Url = {}.", jksFile.getAbsolutePath());
return null;
}
}

@ -26,26 +26,26 @@ public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
logger.debug("ProxyMessage received {}", proxyMessage.getType());
switch (proxyMessage.getType()) {
case ProxyMessage.TYPE_HEARTBEAT:
handleHeartbeatMessage(ctx, proxyMessage);
break;
case ProxyMessage.TYPE_AUTH:
handleAuthMessage(ctx, proxyMessage);
break;
case ProxyMessage.TYPE_CONNECT:
handleConnectMessage(ctx, proxyMessage);
break;
case ProxyMessage.TYPE_DISCONNECT:
handleDisconnectMessage(ctx, proxyMessage);
break;
case ProxyMessage.TYPE_TRANSFER:
handleTransferMessage(ctx, proxyMessage);
break;
case ProxyMessage.TYPE_WRITE_CONTROL:
handleWriteControlMessage(ctx, proxyMessage);
break;
default:
break;
case ProxyMessage.TYPE_HEARTBEAT:
handleHeartbeatMessage(ctx, proxyMessage);
break;
case ProxyMessage.TYPE_AUTH:
handleAuthMessage(ctx, proxyMessage);
break;
case ProxyMessage.TYPE_CONNECT:
handleConnectMessage(ctx, proxyMessage);
break;
case ProxyMessage.TYPE_DISCONNECT:
handleDisconnectMessage(ctx, proxyMessage);
break;
case ProxyMessage.TYPE_TRANSFER:
handleTransferMessage(ctx, proxyMessage);
break;
case ProxyMessage.TYPE_WRITE_CONTROL:
handleWriteControlMessage(ctx, proxyMessage);
break;
default:
break;
}
}
@ -100,8 +100,8 @@ public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
String clientKey = proxyMessage.getUri();
List<Integer> ports = ProxyConfig.getInstance().getClientInetPorts(clientKey);
if (ports == null) {
logger.info("error clientKey {}, close channel", clientKey);
ctx.channel().close();
logger.info("error clientKey {}, {}", clientKey, ctx.channel());
// ctx.channel().close();
return;
}

@ -1,6 +1,14 @@
server.bind=0.0.0.0
server.port=4900
server.ssl.enable=false
server.ssl.bind=0.0.0.0
server.ssl.port=8883
server.ssl.jksPath=test.jks
server.ssl.keyStorePassword=123456
server.ssl.keyManagerPassword=123456
server.ssl.needsClientAuth=false
config.server.bind=0.0.0.0
config.server.port=8090
config.admin.username=admin

@ -1,7 +1,15 @@
server.bind=0.0.0.0
server.port=4900
server.ssl.enable=true
server.ssl.bind=0.0.0.0
server.ssl.port=8883
server.ssl.jksPath=test.jks
server.ssl.keyStorePassword=123456
server.ssl.keyManagerPassword=123456
server.ssl.needsClientAuth=false
config.server.bind=0.0.0.0
config.server.port=8080
config.server.port=8082
config.admin.username=admin
config.admin.password=admin
Loading…
Cancel
Save