Netty+OpenSSL TCP双向认证证书配置

百度加自研终于成功配置了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();
        }

    }
}

参考引用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值