Netty SSL 安全认证

1、Netty面临的安全风险

作为一个高性能的NIO通讯框架,基于Netty的行业应用非常广泛,面临的安全挑战也不同。
下面分析Netty面临的安全挑战。

应用场景一:目前高性能的NIO框架成为RPC的分布式服务框架的基石,内部的各个模块需要进行高性能通信,各模块之间往往采用长链接通信,通过心跳检测保证链路的可靠性。由于RPC框架通常是在内部各模块之间使用,运行在授信的内部安全域中,不直接对外开放接口。因此不需要做握手、黑白名单、SSL/TLS等。
     在这种应用场景下,Netty的安全性是依托企业的防火墙、安全加固操作系统等系统级别安全来保障,它本身并不需要做额外的安全性保护工作。

应用场景二:对于第三方开放的通信框架。如果使用Netty做RPC框架或者私有协议栈,RPC框架面向非授信的第三方开放,例如将内部的一些功能通过服务对外开放出去,此时需要进行安全认证,如果开放的是公网IP,对于安全性要求非常高的服务,例如在线支付、订购等,需要通过SSL/TLS进行通信。原理图如下:
Netty SSL 安全认证_第1张图片

对第三方开放通信框架的接口调用存在三种场景:
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、 测试

Netty SSL 安全认证_第2张图片

分别运行服务端、客户端即可。

2.1.4、单向认证原理

  1. SSL客户端向服务端传送客户端SSL协议的版本号、支持的加密算法种类、产生的随机数、以及其他可选信息;
  2. 服务端返回握手答应,向客户端传送确认SSL协议的版本号、加密算法的种类、随机数以及其他相关信息;
  3. 服务端向客户端发送自己的公钥;
  4. 客户端对服务端的证书进行认证,服务端的合法性校验包含:证书是否过期、发行服务器证书的CA是否可靠、发行者证书的公钥能否正确解开服务器的“发行者的数组签名”、服务器证书上的域名是否和服务器的实际域名相匹配等;
  5. 客户端随机生成一个用于后面通讯的“对称密码”,然后用服务端的公钥对其加密,将加密后的“预主密码”传给服务端;
  6. 服务端将自己的私钥解开加密的“预主密码”,然后执行一系列步骤来产生主密码;
  7. 客户端向服务端发出信息,指明后面的数据通讯将使用主密码为对称密钥,同时通知服务器客户端的握手过程结束;
  8. 服务端向客户端发出信息,指明后面的数据通讯将使用主密码为对称密钥,同时通知客户端服务器端的握手过程结束;
  9. 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双向认证相比单向认证,多了一步服务端发送认证请求消息给客户端,客户端发送自签名证书给服务端进行安全认证的过程。

  1. 相比于客户端,服务端在发送时携带了要求客户端认证的请求信息。
  2. 客服端接收到服务端要求客户端认证的请求消息之后,发送自己的证书信息给服务端。
  3. 服务端对客户端的自签名证书进行认证。

1、单向认证完整代码
2、双向认证完整代码

你可能感兴趣的:(java)