示例工程及测试用的证书文件:https://download.csdn.net/download/zhangfls/12875187
openssl在window本地命令行生成证书的方式:https://blog.csdn.net/zhangfls/article/details/108733072
1、双向加密认证首先要获取到证书,可以先自己生成证书用于测试(实际获取到的公网证书使用方式其实差不多)
(1)可以通过openssl生成证书
(2)首先要生成一份CA根证书,再由该证书生成服务器和客户端的证书
(3)完成基本的SSL/TLS服务器和客户端的双向加密通讯,一共需要生成5份证书
①CA证书
②服务器证书
③服务器密钥
④客户端证书
⑤客户端密钥
(4)、一般证书有多种格式,这里我们用pem格式做示例(linux系统常用)
2、假设我们已经配置了证书,同时配置好了一个简单的nettyTCP连接的客户端和服务器,下面则需要重新配置netty服务器与客户端的SSL管道
(1)、配置netty的SSL可以通过添加pipeline的方式完成,将管道配置在ChannelInitializer实现类的最前面即可
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
private SslContext sslContext;
public MyChannelInitializer(SslContext sslContext) {
this.sslContext = sslContext;
}
@Override
protected void initChannel(SocketChannel channel) {
// 添加SSL安装验证
channel.pipeline().addFirst(sslContext.newHandler(channel.alloc()));
//直接解析原始HEX字节
channel.pipeline().addLast(new StringEncoder(StandardCharsets.ISO_8859_1));
//自定义解析数据handler
channel.pipeline().addLast(new MyServerHandler());
channel.pipeline().addLast(new ByteArrayEncoder());
}
}
(2)包含证书信息的SslContext可以由SslContextBuilder来生成,可以分别生成客户端及服务器的SSL验证protocol。例如下面的certChainFile 为客户端证书,keyFile为客户端密钥,rootFile为CA根证书,服务器的证书配置同理
//引入SSL安全验证
File certChainFile = new File("D:\\OpenSSL-Win64\\bin\\client\\client-cert.pem");
File keyFile = new File("D:\\OpenSSL-Win64\\bin\\client\\client-key.pem");
File rootFile = new File("D:\\OpenSSL-Win64\\bin\\ca\\ca-cert.pem");
SslContext sslCtx = SslContextBuilder.forClient().keyManager(certChainFile, keyFile).trustManager(rootFile).build();
3、一般做双向验证,服务器端可能还会需要客户端证书的详细信息,来确认客户端的身份。可以通过在连接握手完成时,读取SSLSession的信息,来获取到客户端证书的详细信息,如下:
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.pipeline().get(SslHandler.class).handshakeFuture().addListener(
new GenericFutureListener<Future<Channel>>() {
@Override
public void operationComplete(Future<Channel> future) throws Exception {
if(future.isSuccess()){
System.out.println("握手成功");
SSLSession ss = ctx.pipeline().get(SslHandler.class).engine().getSession();
System.out.println("cipherSuite:"+ss.getCipherSuite());
X509Certificate cert = ss.getPeerCertificateChain()[0];
String info = null;
// 获得证书版本
info = String.valueOf(cert.getVersion());
System.out.println("证书版本:" + info);
// 获得证书序列号
info = cert.getSerialNumber().toString(16);
System.out.println("证书序列号:" + info);
// 获得证书有效期
Date beforedate = cert.getNotBefore();
info = new SimpleDateFormat("yyyy/MM/dd").format(beforedate);
System.out.println("证书生效日期:" + info);
Date afterdate = (Date) cert.getNotAfter();
info = new SimpleDateFormat("yyyy/MM/dd").format(afterdate);
System.out.println("证书失效日期:" + info);
// 获得证书主体信息
info = cert.getSubjectDN().getName();
System.out.println("证书拥有者:" + info);
// 获得证书颁发者信息
info = cert.getIssuerDN().getName();
System.out.println("证书颁发者:" + info);
// 获得证书签名算法名称
info = cert.getSigAlgName();
System.out.println("证书签名算法:" + info);
}else{
System.out.println("握手失败");
}
}
});
SocketChannel channel = (SocketChannel) ctx.channel();
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())+" conn:");
System.out.println("IP:" + channel.localAddress().getHostString());
System.out.println("Port:" + channel.localAddress().getPort());
}
4、配置完成后,先启动服务器,再启动客户端,可以看到连接建立成功