百度加自研终于成功配置了netty+TLS双向认证,做个笔记。
TCP双向认证证书配置
1.linux下OpenSSL安装
1.1 openSSL下载
OpenSSL*下载地址:https://oomake.com/download/openssl
1.2 openSSL安装
# tar -xzf openssl-1.0.2f.tar.gz
# cd openssl-1.0.2f
# mkdir /usr/local/openssl
# ./config --prefix=/usr/local/openssl
# make
# make install
查看路径:which openssl,查看版本:openssl version
2.OpenSSL生成证书以及证书转换过程
1.生成服务器端私钥
openssl genrsa -out server.key 2048
2.生成服务器端公钥
openssl rsa -in server.key -pubout -out server.pem
3.生成客户端私钥
openssl genrsa -out client.key 2048
4.生成客户端公钥
openssl rsa -in client.key -pubout -out client.pem
5.生成 CA 私钥
openssl genrsa -out ca.key 2048
6.生成CA的csr文件,保存必要信息
openssl req -new -key ca.key -out ca.csr
7. 生成CA的证书文件crt文件
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt
8.生成服务器端的csr文件,为生成服务器证书做准备
openssl req -new -key server.key -out server.csr
9.生成服务器端证书crt文件
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt
10.生成client 端csr文件,为生成client证书做准备
openssl req -new -key client.key -out client.csr
11.生成client 端证书crt文件
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in client.csr -out client.crt
12.备份服务器私钥
openssl rsa -in server.key -out server_nopwd.key
13.生成服务器端证书
openssl x509 -req -days 365 -in server.csr -signkey server_nopwd.key -out server.crt
14.转换服务端key为PK8
openssl pkcs8 -topk8 -in server.key -out pkcs8_server.key –nocrypt
15.转换客户端key为PK8
openssl pkcs8 -topk8 -in client.key -out pkcs8_client.key -nocrypt
16.转换ca 的cert+key为pfx
openssl pkcs12 -export -in ca.crt -inkey ca.key -out ca.pfx
17.转换ca的 pfx为jks
keytool -importkeystore -srckeystore ca.pfx -destkeystore ca.jks -srcstoretype PKCS12 -deststoretype JKS
18. 转换服务端 的cert+key为pfx
openssl pkcs12 -export -in server.crt -inkey server.key -out server.pfx
19. 转换服务端的 pfx为jks
keytool -importkeystore -srckeystore server.pfx -destkeystore server.jks -srcstoretype PKCS12 -deststoretype JKS
20. 转换客户端 的cert+key为pfx
openssl pkcs12 -export -in client.crt -inkey client.key -out client.pfx
21. 转换客户端的 pfx为jks
keytool -importkeystore -srckeystore client.pfx -destkeystore client.jks -srcstoretype PKCS12 -deststoretype JKS
3.JAVA服务端代码
public class NettyServerWithMtls {
private int port;
public NettyServerWithMtls(int port) {
this.port = port;
}
public void run() throws Exception {
/** 证书 */
String basePath = "D:/idea_workspace/dev-utils/src/main/resources/";
File certFile = new File(basePath + "jks/emqx-dev.crt");
File keyFile = new File(basePath + "jks/emqx-dev.pk8");
File rootFile = new File(basePath + "jks/ca.crt");
File jksServerFile = new File(basePath + "jks/emqx-dev.jks");
File jksCaFile = new File(basePath + "jks/ca.jks");
String jksKeyPassw = "mykeypass";
String jksStorePassw = "mystorepass";
/** ============ TLS - 双向 ===============*/
SSLContext sslContext = null;
/** 方式1:Java SSL(使用jks) - server端keyManagerFactory+trustManagerFactory */
/** keyStore */
//defaultType: jks 或者系统属性: keystore.type"
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream(jksServerFile), jksStorePassw.toCharArray());
//defaultAlgorithm: SunX509 或者系统属性: ssl.KeyManagerFactory.algorithm
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, jksKeyPassw.toCharArray());
/** trustKeyStore */
KeyStore tks = KeyStore.getInstance(KeyStore.getDefaultType());
tks.load(new FileInputStream(jksCaFile), jksStorePassw.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(tks);
//生成SSLContext
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
/** 方式2: httpcore SSLContentBuilder(使用jks) - server端keyMaterial+trustMaterial */
sslContext = SSLContextBuilder.create()
//keyStore
.loadKeyMaterial(jksServerFile, jksStorePassw.toCharArray(), jksKeyPassw.toCharArray())
//trustKeyStore
.loadTrustMaterial(jksCaFile, jksStorePassw.toCharArray())
.build();
//方式1,2: 生成sslEngine
SSLEngine sslEngine = sslContext.createSSLEngine();
//是否客户端模式 - 服务端模式
sslEngine.setUseClientMode(false);
//是否需要验证客户端(双向验证) - 双向验证
sslEngine.setNeedClientAuth(true);
//pipeline.addFirst("ssl", new SslHandler(sslEngine));
/** 方式3: netty sslContextBuilder server端 - 双向tls(使用server端crt+pk8+ca) */
SslContext nettySslContext = SslContextBuilder.forServer(certFile, keyFile)
.trustManager(rootFile)
.clientAuth(ClientAuth.REQUIRE)
.build();
EventLoopGroup bossGroup = new NioEventLoopGroup(); //用于处理服务器端接收客户端连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); //进行网络通信(读写)
try {
ServerBootstrap bootstrap = new ServerBootstrap(); //辅助工具类,用于服务器通道的一系列配置
bootstrap.group(bossGroup, workerGroup) //绑定两个线程组
.channel(NioServerSocketChannel.class) //指定NIO的模式
.childHandler(new ChannelInitializer<SocketChannel>() { //配置具体的数据处理方式
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
/** 方式1,2: ssl handler */
//pipeline.addFirst("ssl", new SslHandler(sslEngine));
/** 方式3: netty sslContext */
pipeline.addFirst("ssl", nettySslContext.newHandler(socketChannel.alloc()));
//业务handler
pipeline.addLast(new ServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128) //设置TCP缓冲区
.childOption(ChannelOption.SO_SNDBUF, 32 * 1024) //设置发送数据缓冲大小
.childOption(ChannelOption.SO_RCVBUF, 32 * 1024) //设置接受数据缓冲大小
.childOption(ChannelOption.SO_KEEPALIVE, true); //保持连接
ChannelFuture future = bootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//do something msg
ByteBuf buf = (ByteBuf) msg;
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
String request = new String(data, "utf-8");
System.out.println("Server Recv from Client: " + request);
//写给客户端
String response = "888";
System.out.println("Server send to Client: " + response);
ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
public static void main(String[] args) throws Exception {
new NettyServerWithMtls(8379).run();
}
}
4.JAVA客户端代码
public class NettyClientWithMtls {
public static void main(String[] args) throws Exception {
String basePath = "D:/idea_workspace/dev-utils/src/main/resources/";
File certFile = new File(basePath + "jks/emqx-dev.crt");
File keyFile = new File(basePath + "jks/emqx-dev.pk8");
File rootFile = new File(basePath + "jks/ca.crt");
File jksServerFile = new File(basePath + "jks/emqx-dev.jks");
File jksCaFile = new File(basePath + "jks/ca.jks");
File jksClientFile = new File(basePath + "jks/emqx-dev-client.jks");
String jksKeyPassw = "mykeypass";
String jksStorePassw = "mystorepass";
/** ============ TLS - 双向 ===============*/
SSLContext sslContext = null;
/** 方式1: Java SSL(使用jks) - 客户端keyManagerFactory+服务端trustManagerFactory */
//keyStore
//defaultType: jks 或者系统属性: keystore.type"
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream(jksClientFile), jksStorePassw.toCharArray());
//defaultAlgorithm: SunX509 或者系统属性: ssl.KeyManagerFactory.algorithm
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, jksKeyPassw.toCharArray());
//trustKeyStore
KeyStore tks = KeyStore.getInstance(KeyStore.getDefaultType());
//此处暴露server.jks 或 ca.jks均可通过验证
tks.load(new FileInputStream(jksCaFile), jksStorePassw.toCharArray());
//tks.load(new FileInputStream(jksServerFile), jksStorePassw.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(tks);
//生成SSLContext
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
/** 方式2: httpcore SSLContentBuilder(使用jks) - 客户端keyMaterial+服务端trustMaterial */
sslContext = SSLContextBuilder.create()
//keyStore
.loadKeyMaterial(jksClientFile, jksStorePassw.toCharArray(), jksKeyPassw.toCharArray())
//trustKeyStore
.loadTrustMaterial(jksCaFile, jksStorePassw.toCharArray())
.build();
//方式1,2: 生成sslEngine
SSLEngine sslEngine = sslContext.createSSLEngine();
//是否客户端模式 - 客户端模式
sslEngine.setUseClientMode(true);
//是否需要验证客户端(双向验证) - 双向验证
sslEngine.setNeedClientAuth(true);
//pipeline.addFirst("ssl", new SslHandler(sslEngine));
/** 方式3: netty sslContextBuilder(使用客户端cert+key.pk8,ca) - 客户端keyManager+ca端trustManager */
SslContext nettySslContext = SslContextBuilder.forClient()
//客户端crt+key.pk8
.keyManager(certFile, keyFile)
//ca根证书
.trustManager(rootFile)
//双向验证
.clientAuth(ClientAuth.REQUIRE)
.build();
//pipeline.addFirst("ssl", nettySslContext.newHandler(socketChannel.alloc()));
EventLoopGroup workerGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
/** 方式1,2: ssl handler(trust ca) */
//pipeline.addFirst("ssl", new SslHandler(sslEngine));
/** 方式3: netty sslContext */
pipeline.addFirst("ssl", nettySslContext.newHandler(socketChannel.alloc()));
pipeline.addLast(new ClientHandler());
}
});
ChannelFuture future = bootstrap.connect("127.0.0.1", 8379).sync();
String sendMsg = "777";
System.out.println("Client send to Server:" + sendMsg);
future.channel().writeAndFlush(Unpooled.copiedBuffer(sendMsg.getBytes()));
future.channel().closeFuture().sync();
workerGroup.shutdownGracefully();
}
public static class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
ByteBuf buf = (ByteBuf) msg;
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
System.out.println("Client recv from Server:" + new String(data).trim());
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
}