基于Netty的TLS使用

2019/01/15 Java

基本概念

SSL/TLS加密原理

SSL/TLS位于OSI七层模型的表示层,用处最多在HTTPS加密。

SSL/TLS协议的基本思路是采用公钥加密法,也就是说,客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。

OSI模型

SSL/TLS协议运行机制的概述

TLS握手过程

TLS握手过程有单向验证和双向验证之分,简单解释一下:

  • 单向验证就是server端将证书发送给客户端,客户端验证server端证书的合法性等,例如百度、新浪、google等普通的https网站;
  • 双向验证则是不仅客户端会验证server端的合法性,同时server端也会验证客户端的合法性,例如银行网银登陆,支付宝登陆交易等。

握手流程如上图所示:

具体抓包可以参考

SSL/TLS 握手过程详解

使用wireshark观察SSL/TLS握手过程–双向认证/单向认证

浅谈HTTPS(SSL/TLS)原理

证书知识

  • PKI

    PKI(Public key Infrastructure)即公钥基础设施,简单的说PKI技术就是利用公钥理论和技术建立的提供信息安全服务的基础设施,该体系在统一的安全认证标准和规范基础上提供在线身份认证,是CA认证、数字证书、数字签名以及相关安全应用组件模块的集合。

  • CA

    认证机构CA(Certificate Authority)在https中是一个很重要的角色,CA是PKI的核心执行机构,是PKI的主要组成部分,通常称之为认证中心,从广义上讲,认证中心还应该包括证书申请注册机构RA(Registration Authority),它是数字证书的申请注册、证书签发的管理机构。

  • 公钥

    公钥(Public-key),即所谓的公共证书,由CA中心颁发的合法文件,可以在互联网传播,谁都可以轻易获取到。公钥证书文件的扩展名实际上只是一种使用习惯上的区别,后缀包括但不仅限于crt、cer、key、der、pem,pem可能包含了公钥和私钥文件,通常可以从pem文件中导出公钥和私钥。

  • 私钥

    私钥(private-key),即通常就叫所谓的私钥,私钥在生成CSR文件的时候同时生产,后缀通常为.key,由使用者自己保管,不可在互联网传播,极其重要。

  • OCSP

    OCSP(Online Certificate Status Protocol),即在线证书状态协议,其实就是一个请求应答模式的协议,用于在线的查询证书吊销状态,而无需查询CRL,在对证书状态实时性要求较高的场合适用于使用OCSP来查询当前证书状态。

  • CRL

    CRL(Certificate Revocation List),即证书吊销列表,它指定了一套证书发布者认为无效的证书,CRL一定是被CA所签署的,它可以使用与签发证书相同的私钥,也可以使用专门的CRL签发私钥,CRL中包含了被吊销证书的序列号。通常情况下,公钥证书中会写出该证书的CA中心CRL地址。

  • 对称加密

    对称加密技术(Stmmetric Cryptographic technique),即对称加密技术,发送方和接收方使用相同的算法来进行加解密。

    • 优点:算法公开,计算量小,加密速度快,效率高。
    • 缺点:加解密双方使用同样的密钥,安全性得不到保障。
    • 常见算法:

    ​ DES、3DES、TDEA、RC4、RC5、AES等。

  • 非对称加密

    非对称加密技术(asymmetric crypto-graphic technique),即采用了两种相关变换的密码技术,也就是公钥和私钥,公钥加密私钥解密,私钥加密公钥解密。

    • 常见算法:

    ​ RSA、DSA、ECC、DH等。

SSL 证书格式普及,PEM、CER、JKS、PKCS12

Netty原理

Netty是高性能、异步事件驱动的非阻塞(NIO)Reactor模式的socket通信框架,采用异步非阻塞方式工作。

彻底理解Netty,这一篇文章就够了

Java NIO浅析

Java高阶必备之Netty基础原理

基于Netty,可以很方便搭建自己自己的HTTP服务器,FTP服务器,UDP服务器,RPC服务器,WebSocket服务器,Redis的Proxy服务器,MySQL的Proxy服务器等等。

基于Netty服务端TLS开发

  • 自定义X509TrustManager
package com.tencent.cloud.iov.conn.netty.handler;

import org.springframework.util.ResourceUtils;

import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

/**
 * @author:liming
 * @data:2019/1/15
 * @decription
 */
// 使用自定义的 MyTrustManager 产生信任库
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

class MyTrustManager implements X509TrustManager {
    // 相关的 jks 文件及其密码定义
    private final static String TRUST_STORE_PASSWORD = "123456";
    X509TrustManager xtm;

    public MyTrustManager() throws Exception {
        // 载入 jks 文件
        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(new FileInputStream(ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + "trustkeystore.jks")), TRUST_STORE_PASSWORD.toCharArray());
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509", "SunJSSE");
        tmf.init(ks);
        TrustManager[] tms = tmf.getTrustManagers();
        // 筛选出 X509 格式的信任证书
        for (int i = 0; i < tms.length; i++) {
            if (tms[i] instanceof X509TrustManager) {
                xtm = (X509TrustManager) tms[i];
                return;
            }
        }
    }

    // 服务端检验客户端证书的接口
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        // TODO throw exception
        System.out.println(authType);
    }

    // 客户端检验服务端证书的接口
    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//        try {
//            xtm.checkServerTrusted(chain, authType);
//        } catch (CertificateException excep) {
//            System.out.println(excep.getMessage());
//            throw excep;
//        }
        System.out.println(authType);
    }

    // 获取可接受的发行者
    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return xtm.getAcceptedIssuers();
//        return null;
    }
}

//注意:
//
//1. 当服务端代码 setNeedClientAuth(False) 时,客户端的 MyTrustManager 实现了 X509TrustManager 后,
// 如果 checkServerTrusted() 方法的实现为空,则无论服务端使用什么证书,客户端都会默认接受;
// 如果要对服务端证书进行检查,还需要像清单 5 中的代码片段那样,捕捉异常并处理。
//
//2.getAcceptedIssuers() 方法通常不需要具体实现,但是当服务端要求检验客户端身份,
// 也即 setNeedClientAuth(True) 时,服务端需也需要具体实现 X509TrustManager,且 getAcceptedIssuers() 方法要如清单 5 中注释部分代码那样实现。
  • SSLContext工厂
/**
 * @author:liming
 * @data:2019/1/15
 * @decription
 */
import io.netty.util.internal.SystemPropertyUtil;
import org.springframework.util.ResourceUtils;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.security.KeyStore;

public final class SecureChatSslContextFactory {

    private static final String PROTOCOL = "TLS";
    private static final SSLContext SERVER_CONTEXT;
    private static final String ALGORITHM_SUN_X509="SunX509";
    private static final String ALGORITHM="ssl.KeyManagerFactory.algorithm";

    private static String SERVER_KEY_STORE_PASSWORD = "password";
    private static String SERVER_TRUST_KEY_STORE_PASSWORD = "password";

    static {
        String algorithm = SystemPropertyUtil.get(ALGORITHM);
        if (algorithm == null) {
            algorithm = ALGORITHM_SUN_X509;
        }
        String defaultAlgorithm = KeyManagerFactory.getDefaultAlgorithm();

        SSLContext serverContext;
        try {
            // Key Store
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(new FileInputStream(ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + "keystore.jks")), SERVER_KEY_STORE_PASSWORD.toCharArray());
            KeyStore tks = KeyStore.getInstance("JKS");
            tks.load(new FileInputStream(ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + "trustkeystore.jks")), SERVER_TRUST_KEY_STORE_PASSWORD.toCharArray());

            // Set up key manager factory to use our key store
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
            kmf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray());

//            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
//            tmf.init(tks);

            // Initialize the SSLContext to work with our key managers.
            serverContext = SSLContext.getInstance(PROTOCOL);
//            serverContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

            TrustManager[] tms=new TrustManager[]{new MyTrustManager()};
            serverContext.init(kmf.getKeyManagers(), tms, null);

        } catch (Exception e) {
            throw new Error("Failed to initialize the server-side SSLContext", e);
        }

        SERVER_CONTEXT = serverContext;
    }

    public static SSLContext getServerContext() {
        return SERVER_CONTEXT;
    }
}

  • SSLEngine初始化
SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine();
engine.setUseClientMode(false);
engine.setNeedClientAuth(true);

pipeline.addLast("ssl", new SslHandler(engine));

Netty实现安全Socket通信

Search

    Table of Contents