Netty SSL 安全认证
Posted 沸羊羊一个
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty SSL 安全认证相关的知识,希望对你有一定的参考价值。
1、Netty面临的安全风险
作为一个高性能的NIO通讯框架,基于Netty的行业应用非常广泛,面临的安全挑战也不同。
下面分析Netty面临的安全挑战。
应用场景一:目前高性能的NIO框架成为RPC的分布式服务框架的基石,内部的各个模块需要进行高性能通信,各模块之间往往采用长链接通信,通过心跳检测保证链路的可靠性。由于RPC框架通常是在内部各模块之间使用,运行在授信的内部安全域中,不直接对外开放接口。因此不需要做握手、黑白名单、SSL/TLS等。
在这种应用场景下,Netty的安全性是依托企业的防火墙、安全加固操作系统等系统级别安全来保障,它本身并不需要做额外的安全性保护工作。
应用场景二:对于第三方开放的通信框架。如果使用Netty做RPC框架或者私有协议栈,RPC框架面向非授信的第三方开放,例如将内部的一些功能通过服务对外开放出去,此时需要进行安全认证,如果开放的是公网IP,对于安全性要求非常高的服务,例如在线支付、订购等,需要通过SSL/TLS进行通信。原理图如下:
对第三方开放通信框架的接口调用存在三种场景:
1. 在企业内网,开放给内部其它模块调用的服务,通常不需要进行安全认证和SSL/TLS传输;
2. 在企业内网,被外部其它模块调用的服务,往往需要利用IP黑名单、握手登录等方式进行安全认证,认证通过后双方使用普通的Socket进行通信,如果认证失败,则拒绝客户端连接;
3. 开放给企业外部第三方应用访问的服务,往往需要监听公网IP(通常是防火墙的IP地址),由于对第三方服务调用者的监管存在诸多困难,或者无法有效监听,这些第三方应用实际是非授信的,为了有效应对安全风险,对于敏感的服务往往通过SSL/TSL进行安全传输。
应用场景三:应用层协议的安全性,作为高性能、异步事件驱动的NIO框架,Netty非常适合构建上层的应用层协议。由于绝大多数应用层协议都是公有的,这意味着底层的Netty需要向上层提供通信层的安全传输,也就是需要支持SSL/TLS。
2、Netty SSL安全特性
Netty通过SslHandler提供了对SSL的支持,它支持的SSL协议类型包括:SSL V2、SSL V3和TLS
2.1、SSL单向认证
单向认证:客户端只验证服务端的合法性,服务端不验证客户端。
2.1.1、利用JDK的keytool工具生成自签名证书
对应keytool的使用及命令说明可参考JDK自带工具keytool生成ssl证书搭建tomcat+https协议
(1)生成Netty服务器私钥和证书仓库:
keytool -genkey -alias securechat -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass sNetty -storepass sNetty -keystore sChat.jks
(2)生成Netty服务端自签名证书:
keytool -genkey -alias smcc -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass cNetty -storepass cNetty -keystore cChat.jks
(3)生成客户端的密钥对和证书仓库,用于服务端的证书保存到客户端的授信证书仓库中:
keytool -export -alias securechat -keystore sChat.jks -storepass sNetty -file sChat.cer
(4)将Netty服务端的证书导入到客户端的证书仓库中:
keytool -import -trustcacerts -alias securechat -file sChat.cer -storepass cNetty -keystore cChat.jks
2.1.2、核心代码
服务端
(1)因为是客户端认证服务端,因此服务端需要设置和加载私钥仓库KeyStore,创建服务端的上下文(SSLContext)
//密钥管理器
KeyManagerFactory kmf = null;
if(pkPath!=null)
//密钥库KeyStore
KeyStore ks = KeyStore.getInstance("JKS");
//加载客户端证书
in = new FileInputStream(pkPath);
//加载服务端的KeyStore ;sNetty是生成仓库时设置的密码,用于检查密钥库完整性的密码
ks.load(in, "sNetty".toCharArray());
kmf = KeyManagerFactory.getInstance("SunX509");
//初始化密钥管理器
kmf.init(ks, "sNetty".toCharArray());
//获取安全套接字协议(TLS协议)的对象
SERVER_CONTEXT= SSLContext.getInstance(PROTOCOL);
//初始化此上下文
//参数一:认证的密钥 参数二:对等信任认证 参数三:伪随机数生成器 。 由于单向认证,服务端不用验证客户端,所以第二个参数为null
SERVER_CONTEXT.init(kmf.getKeyManagers(), null, null);
(2)服务端的SSLContext创建完成后,利用SSLContext创建SSL引擎SSLEngine,设置SSLEngine为服务器模式。由于不需要对客户端进行认证,因此NeedClientAuth不需要额外设置,使用默认值false。
engine.setUseClientMode(false);//设置为服务器模式
//engine.setNeedClientAuth(false);//不需要客户端认证,默认为false,故不需要写这行。
(3)把SslHandler添加到管道中
pipeline.addLast("ssl", new SslHandler(engine));
客户端
(1)客户端和服务端类似,由于是客户端认证服务端,因此,客户端只需要加载存放服务端CA的证书仓库即可。
加载证书仓库完成后,初始化SSLContext,设置信任证书TrustManager。
//信任库
TrustManagerFactory tf = null;
if (caPath != null)
//密钥库KeyStore
KeyStore tks = KeyStore.getInstance("JKS");
//加载客户端证书
tIN = new FileInputStream(caPath);
tks.load(tIN, "cNetty".toCharArray());
tf = TrustManagerFactory.getInstance("SunX509");
// 初始化信任库
tf.init(tks);
CLIENT_CONTEXT = SSLContext.getInstance(PROTOCOL);
//设置信任证书
CLIENT_CONTEXT.init(null,tf == null ? null : tf.getTrustManagers(), null);
(2)客户端的SSLContext创建完成后,利用SSLContext创建SSL引擎SSLEngine,设置SSLEngine为客户方模式。
engine.setUseClientMode(true);//客户方模式
(3)把SslHandler添加到管道中
pipeline.addLast("ssl", new SslHandler(engine));
2.1.3、 测试
分别运行服务端、客户端即可。
2.1.4、单向认证原理
- SSL客户端向服务端传送客户端SSL协议的版本号、支持的加密算法种类、产生的随机数、以及其他可选信息;
- 服务端返回握手答应,向客户端传送确认SSL协议的版本号、加密算法的种类、随机数以及其他相关信息;
- 服务端向客户端发送自己的公钥;
- 客户端对服务端的证书进行认证,服务端的合法性校验包含:证书是否过期、发行服务器证书的CA是否可靠、发行者证书的公钥能否正确解开服务器的“发行者的数组签名”、服务器证书上的域名是否和服务器的实际域名相匹配等;
- 客户端随机生成一个用于后面通讯的“对称密码”,然后用服务端的公钥对其加密,将加密后的“预主密码”传给服务端;
- 服务端将自己的私钥解开加密的“预主密码”,然后执行一系列步骤来产生主密码;
- 客户端向服务端发出信息,指明后面的数据通讯将使用主密码为对称密钥,同时通知服务器客户端的握手过程结束;
- 服务端向客户端发出信息,指明后面的数据通讯将使用主密码为对称密钥,同时通知客户端服务器端的握手过程结束;
- SSL的握手部分结束,SSL安全通道建立,客户端和服务端开始使用相同的对称密钥对数据进行加密,然后通过Socket进行传输
2.1、SSL双向认证
与单向认证不同的是服务端也需要对客户端进行安全认证,这就意味着客户端的自签名证书
也需要导入到服务器的数组证书仓库中。
2.2.1、利用JDK的keytool工具生成自签名证书
前面的生成和单向一样,然后继续
(1)生成客户端的自签名证书:
keytool -export -alias smcc -keystore cChat.jks -storepass cNetty -file cChat.cer
(2)将客户端的自签名证书导入到服务端的信任证书仓库
keytool -import -trustcacerts -alias smcc -file cChat.cer -storepass sNetty -keystore sChat.jks
2.2.2、服务端
(1)由于服务端需要对客户端进行验证,所以在初始化服务端SSLContext的时候需要加载证书仓库。
//信任库
TrustManagerFactory tf = null;
if (caPath != null)
KeyStore tks = KeyStore.getInstance("JKS");
tIN = new FileInputStream(caPath);
tks.load(tIN, "sNetty".toCharArray());
tf = TrustManagerFactory.getInstance("SunX509");
tf.init(tks);
SERVER_CONTEXT= SSLContext.getInstance(PROTOCOL);
//初始化此上下文
//参数一:认证的密钥 参数二:对等信任认证 参数三:伪随机数生成器 。 由于单向认证,服务端不用验证客户端,所以第二个参数为null
SERVER_CONTEXT.init(kmf.getKeyManagers(),tf.getTrustManagers(), null);
(2)、创建SSLEngine后,设置客户端认证
engine.setUseClientMode(false);//设置服务端模式
engine.setNeedClientAuth(true);//需要客户端验证
2.2.3、客户端
(1)由于服务端需要认证客户端的证书,因此,需要初始化和加载私钥仓库,向服务端发送公钥。初始化KeyStore的代码如下:
KeyManagerFactory kmf = null;
if (pkPath != null)
KeyStore ks = KeyStore.getInstance("JKS");
in = new FileInputStream(pkPath);
ks.load(in, "cNetty".toCharArray());
kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "cNetty".toCharArray());
SERVER_CONTEXT= SSLContext.getInstance(PROTOCOL);
//初始化此上下文
//参数一:认证的密钥 参数二:对等信任认证 参数三:伪随机数生成器 。 由于单向认证,服务端不用验证客户端,所以第二个参数为null
CLIENT_CONTEXT.init(kmf.getKeyManagers(),tf.getTrustManagers(), null);
除了以上得代码,双向认证的其余代码与单向基本一样。
2.2.4、双向认证原理
10、SSL双向认证相比单向认证,多了一步服务端发送认证请求消息给客户端,客户端发送自签名证书给服务端进行安全认证的过程。
- 相比于客户端,服务端在发送时携带了要求客户端认证的请求信息。
- 客服端接收到服务端要求客户端认证的请求消息之后,发送自己的证书信息给服务端。
- 服务端对客户端的自签名证书进行认证。
以上是关于Netty SSL 安全认证的主要内容,如果未能解决你的问题,请参考以下文章