转载

Netty SSL双向验证

一· 快速命令

1.生成ca证书

openssl req -new -x509 -keyout ca.key -out ca.crt -days 36500

在本目录得到 ca.key 和 ca.crt 文件

2.生成服务端和客户端私钥

openssl genrsa -des3 -out server.key 1024

openssl genrsa -des3 -out client.key 1024

3.根据 key 生成 csr 文件

openssl req -new -key server.key -out server.csr

openssl req -new -key client.key -out client.csr

4.根据 ca 证书 server.csr 和 client.csr 生成 x509 证书

openssl x509 -req -days 36500 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt

openssl x509 -req -days 36500 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt

5.将 key 文件进行 PKCS#8 编码

openssl pkcs8 -topk8 -in server.key -out pkcs8_server.key -nocrypt

openssl pkcs8 -topk8 -in client.key -out pkcs8_client.key -nocrypt

最后得到有用的文件分别为

  • 服务器端: ca.crt、server.crt、pkcs8_server.key
  • 客户端端: ca.crt、client.crt、pkcs8_client.key

6. 查看命令

openssl 查看证书细节
打印证书的过期时间
openssl x509 -in signed.crt -noout -dates
打印出证书的内容:
openssl x509 -in cert.pem -noout -text
打印出证书的系列号
openssl x509 -in cert.pem -noout -serial
打印出证书的拥有者名字
openssl x509 -in cert.pem -noout -subject
以RFC2253规定的格式打印出证书的拥有者名字
openssl x509 -in cert.pem -noout -subject -nameopt RFC2253
在支持UTF8的终端一行过打印出证书的拥有者名字
openssl x509 -in cert.pem -noout -subject -nameopt oneline -nameopt -escmsb
打印出证书的MD5特征参数
openssl x509 -in cert.pem -noout -fingerprint
打印出证书的SHA特征参数
openssl x509 -sha1 -in cert.pem -noout -fingerprint
把PEM格式的证书转化成DER格式
openssl x509 -in cert.pem -inform PEM -out cert.der -outform DER
把一个证书转化成CSR
openssl x509 -x509toreq -in cert.pem -out req.pem -signkey key.pem
给一个CSR进行处理,颁发字签名证书,增加CA扩展项
openssl x509 -req -in careq.pem -extfile openssl.cnf -extensions v3_ca -signkey key.pem -out cacert.pem
给一个CSR签名,增加用户证书扩展项
openssl x509 -req -in req.pem -extfile openssl.cnf -extensions v3_usr -CA cacert.pem -CAkey key.pem -CAcreateserial
查看csr文件细节:
openssl req -in my.csr -noout -text

二· 部分参考代码

服务端的代码:

@Slf4j
public class HidsSslContextBuilder {
    private final static String serverCrt = "/static/keys/server.crt";
    private final static String serverKey = "/static/keys/pkcs8_server.key";
    private final static String caCrt = "/static/keys/ca.crt";
    private final static String keyPassword = "";


    public static SslContext build(ClientAuth clientAuth) {
        InputStream certInput = null;
        InputStream priKeyInput = null;
        InputStream caInput = null;
        try {
            certInput = HidsSslContextBuilder.class.getResourceAsStream(serverCrt);
            priKeyInput = HidsSslContextBuilder.class.getResourceAsStream(serverKey);
            caInput = HidsSslContextBuilder.class.getResourceAsStream(caCrt);
            return SslContextBuilder.forServer(certInput, priKeyInput)
                    .clientAuth(clientAuth)
                    .trustManager(caInput).build();
        } catch (Throwable e) {
            log.error("HidsSslContextBuilder", e);
        } finally {
            IOUtils.closeQuietly(certInput);
            IOUtils.closeQuietly(priKeyInput);
            IOUtils.closeQuietly(caInput);
        }
        return null;
    }


    public static SslContext buildSelfSignedCer() {
        try {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            return SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
                    .build();
        } catch (Throwable e) {
            log.error("buildSelfSignedCer", e);
        }
        return null;
    }
}

...
@Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();

        // INBOUND: from head to tail
        // OUTBOUND: from tail to head
        // ssl
        if (sslContext != null) {
            pipeline.addLast(sslContext.newHandler(socketChannel.alloc()));
        }

        pipeline.addLast("TLVDecoder", new TLVDecoder());
        pipeline.addLast("TLVEncoder", new TLVEncoder());
        pipeline.addLast("Decompressor", new Decompressor());
        pipeline.addLast("Compressor", new Compressor());
        pipeline.addLast("tlvChannelHandler", tlvChannelHandler);

        // 增加channel对应的数据
        prepareChannelContext(socketChannel);
    }

客户端的测试代码

public class ClientChannelTest {

    @Test
    public void testClient() throws Throwable {
        String host = "0.0.0.0";
        int port = 8888;

        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            final SslContext sslCtx = SslContextBuilder.forClient()
                    // 双向验证
//                    .keyManager(this.getClass().getResourceAsStream("/keys/client.crt"),
//                            this.getClass().getResourceAsStream("/keys/pkcs8_client.key"))

                    // CA证书,验证对方证书
                    .trustManager(this.getClass().getResourceAsStream("/keys/ca.crt"))

                    // 不验证SERVER
                    // .trustManager(InsecureTrustManagerFactory.INSTANCE)
                    .build();

            Bootstrap b = new Bootstrap(); // (1)
            b.group(workerGroup); // (2)
            b.channel(NioSocketChannel.class); // (3)
            b.option(ChannelOption.SO_KEEPALIVE, true); // (4)
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()));
                    ch.pipeline().addLast(new TLVDecoder());
                    ch.pipeline().addLast(new TLVEncoder());
                    ch.pipeline().addLast("Decompressor", new Decompressor());
                    ch.pipeline().addLast("Compressor", new Compressor());
                    ch.pipeline().addLast(new ClientHandler());
                }
            });

            // Start the client.
            ChannelFuture f = b.connect(host, port).sync(); // (5)

            // Wait until the connection is closed.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }

    }
}

在Channel上绑定链接上下文信息,类似Session的功能,存储在Channel中的 Attr 中。

public class ChannelUtils {
    public static ClientContext getOrCreate(Channel socketChannel) {
        AttributeKey<ClientContext> key = AttributeKey.valueOf(ClientContext.class.toString());
        Attribute<ClientContext> attr = socketChannel.attr(key);
        if (attr.get() == null) {
            attr.set(createClientContext(socketChannel));
        }
        return attr.get();
    }

    private static ClientContext createClientContext(Channel socketChannel) {
        return new DefaultClientContext(socketChannel.pipeline());
    }
}

三· 参考

  • Netty实现SSL双向验证完整实例
  • openssl req(生成证书请求和自建CA)
  • 如何创建私有 CA 并签发证书
  • keytool制作CA根证书以及颁发二级证书
  • SSL-用Keytool和OpenSSL生成和签发数字证书
  • netty实现SSL 双向认证与用jmeter测试
原文  http://www.easysb.cn/2019/07/492.html
正文到此结束
Loading...