关于https的双向认证详解
    
   
原doc文档导出pdf
    
     基本描述
    
   
Https是在基本的http通讯协议上增加的tls安全协议,tls安全协议需要ssl安全证书作为加解密要素,双向认证是指在客户端确认服务的是否可信任的单向认证基础上增加服务端的对客户端的认证。
    
     主要内容
    
   
    
     https通信协议
    
   
    
     是以安全为目标的
    
    
     HTTP
    
    
     通道,简单讲是
    
    
     HTTP
    
    
     的安全版。即
    
    
     HTTP
    
    
     下加入
    
    
     SSL
    
    
     层,
    
    
     HTTPS
    
    
     的安全基础是
    
    
     SSL
    
    
     ,因此加密的详细内容就需要
    
    
     SSL
    
   
    
     参见
    
    
     
      百度https百科
     
    
   
    
     tls安全协议
    
   
SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密
    
     参见
    
    
     
      百度SSL
     
    
   
    
     ssl安全证书
    
   
    
     SSL
    
    
     证书就是遵守
    
    
     SSL
    
    
     协议,由受信任的数字证书颁发机构
    
    
     CA
    
    
     ,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能
    
   
    参见
    
     百度ssl证书
    
   
    
     证书链
    
   
    
     
      证书链
     
    
    
     的描述,证书链的可信传递机制,以及根证书的来源和查看方式
    
   
    
     证书格式
    
   
常见证书格式和转换
    
     https://blog.csdn.net/justinjing0612/article/details/7770301
    
   
    
     tls认证过程
    
   
    
     SSL
    
    
     认证是指
    
    
     
      客户端
     
    
    
     到服务器端的认证。主要用来提供对用户和服务器的认证;对传送的数据进行加密和隐藏;确保数据在传送中不被改变,即数据的完整性,现已成为该领域中全球化的标准,即所谓的单向认证
    
   
    参见
    
     百度ssl认证
    
   
    
     其他内容
    
   
HTTPS实战之单向验证和双向验证
    
     https://www.jianshu.com/p/119c4dbb7225
    
   
浅谈HTTPS(SSL/TLS)原理
    
     https://www.jianshu.com/p/41f7ae43e37b
    
   
HTTPS通信中的身份认证机制
    
     https://blog.csdn.net/bravegogo/article/details/60766773
    
   
SSL双向认证以及证书的制作和使用-https+客户端身份验证
    
     https://blog.csdn.net/soarheaven/article/details/78784848
    
   
X – Certificate and Key management证书管理工具
    
     https://hohnstaedt.de/xca/index.php
    
   
ssl证书相当于安全的钥匙,如果私钥外泄被恶意盗用,会存在安全问题
    
     双向认证的常用场景
    
   
    
     银行网银U盾使用
    
   
专业版登录支持U盾认证多重认证
资金动账类交易U盾再次认证确认客户端身份
    
     金融支付工具安全数字证书
    
   
支付宝数字证书app下载安装可以提高app支付安全,支付额度
常用pc安装数字证书免除重复的其他安全校验
    
     其他对通信安全有较高要求的都可以应用此https双向认证
    
   
主要成本在于向具有CA资质的机构申请有效的证书,非CA机构的自制根证书及证书链管理需要自行负责
    
     https应用的使用示例
    
   
    
     工具版本说明
    
   
本地操作系统版本:win10
本地tomcat版本9
本地java版本1.8
证书管理工具xca 2.1.2
Keytool界面工具1.6
    
     http://enkj.jb51.net:81/201703/tools/keytool_jb51.rar
    
   
    
     前提概要
    
   
客户端和服务端角色根据通信请求来确定,客户端和服务端需要保持支持一致的TLS安全协议版本
    
     单向认证说明
    
   
单向认证首先需要申请制作证书链
对于客户端请求服务端,比较常见的客户端是指浏览网页使用的浏览器(chrome,IE,firefox等)以及移动客户端(ios或android app),服务端一般指客户端请求目标服务的服务提供者
    
     双向认证Java的https客户端请求
    
   
    
     Keystool工具使用
    
   
请参考网络使用说明
依照java的keytool命令界面化工具,生成java专有的jks格式容器文件
    
     依赖包
    
   
<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.9</version>
        </dependency>
    
     Java client demo
    
   
package com.web.ssl.twoway.ssltwoway;
import com.web.ssl.twoway.ssltwoway.Constants;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import com.web.ssl.twoway.ssltwoway.Constants;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class SSLhttps {
    public static void main(String[] args) throws Exception {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        keyStore.load(new FileInputStream(new File(Constants.clientPrivateFile)), Constants.clientPrivatePassword.toCharArray());
        SSLContext sslcontext = SSLContexts.custom()
                //忽略掉对服务器端证书的校验
                .loadTrustMaterial(new TrustStrategy() {
                    public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                        return true;
                    }
                })
                //加载服务端提供的truststore(如果服务器提供truststore的话就不用忽略对服务器端证书的校验了)
                .loadTrustMaterial(new File(Constants.trustAcFile), Constants.trustAcFilePassword.toCharArray(),
                        new TrustSelfSignedStrategy())
                .loadKeyMaterial(keyStore, Constants.clientPrivatePassword.toCharArray())
                .build();
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                sslcontext,
                new String[]{"TLSv1"},
                null,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        CloseableHttpClient httpclient = HttpClients.custom()
                .setSSLSocketFactory(sslConnectionSocketFactory)//取消改行注释不导入运行环境的客户端证书 AAA
                .build();
        try {
            HttpGet httpget = new HttpGet("https://localhost:8443/");
//            若注释 AAA 行的内容,请求需要双向认证的服务会出现以下异常
//unable to find valid certification path to requested target
            System.out.println("Executing request " + httpget.getRequestLine());
            CloseableHttpResponse response = httpclient.execute(httpget);
            try {
                HttpEntity entity = response.getEntity();
                System.out.println(response.getStatusLine());
                System.out.println(IOUtils.toString(entity.getContent()));
                EntityUtils.consume(entity);
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
    }
}
    
     Java httpsServer demo
    
   
package com.web.ssl.twoway.ssltwoway;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.security.KeyStore;
import java.security.cert.Certificate;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.security.cert.X509Certificate;
public class HTTPSServer {
    private boolean isServerDone = false;
    public static void main(String[] args) {
        HTTPSServer server = new HTTPSServer();
        server.run();
    }
    // Create the and initialize the SSLContext
    private SSLContext createSSLContext() {
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(new FileInputStream(Constants.serverPrivateFile), Constants.serverPrivatePassword.toCharArray());
            // Create key manager
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
            keyManagerFactory.init(keyStore, Constants.serverPrivatePassword.toCharArray());
            KeyManager[] km = keyManagerFactory.getKeyManagers();
            KeyStore keyStoreTrust = KeyStore.getInstance("JKS");
            keyStoreTrust.load(new FileInputStream(Constants.trustAcFile), Constants.trustAcFilePassword.toCharArray());
            // Create trust manager
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
            trustManagerFactory.init(keyStoreTrust);
            TrustManager[] tm = trustManagerFactory.getTrustManagers();
            // Initialize SSLContext
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(km, tm, null);
            return sslContext;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }
    // Start to run the server
    public void run() {
        SSLContext sslContext = this.createSSLContext();
        try {
            // Create server socket factory
            SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
            // Create server socket
            SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(Constants.DEFAULT_PORT);
            System.out.println("SSL server started");
            while (!isServerDone) {
                SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
                // Start the server thread
                new ServerThread(sslSocket).start();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    // Thread handling the socket from client
    static class ServerThread extends Thread {
        private SSLSocket sslSocket = null;
        ServerThread(SSLSocket sslSocketInit) {
            sslSocket = sslSocketInit;
            sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites());
            sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols());
            sslSocket.setNeedClientAuth(true);
            sslSocket.setWantClientAuth(true);
            sslSocket.setEnableSessionCreation(true);
        }
        public void run() {
            try {
                // Start handshake
                sslSocket.startHandshake();
                // Get session after the connection is established
                SSLSession sslSession = sslSocket.getSession();
                System.out.println("SSLSession :");
                System.out.println("\tProtocol : " + sslSession.getProtocol());
                System.out.println("\tCipher suite : " + sslSession.getCipherSuite());
                // Start handling application content
                InputStream inputStream = sslSocket.getInputStream();
                OutputStream outputStream = sslSocket.getOutputStream();
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(outputStream));
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    System.out.println("Inut : " + line);
                    if (line.trim().isEmpty()) {
                        break;
                    }
                }
                boolean clientCertValid = false;
                try {
                    Certificate[] c1 = sslSession.getLocalCertificates();
                    for (Certificate c1One : c1) {
                        System.out.println("getLocalCertificates : " + c1One.toString());
                    }
                    X509Certificate[] c2 = sslSession.getPeerCertificateChain();
                    for (X509Certificate c1One : c2) {
                        System.out.println("getPeerCertificateChain : " + c1One.toString());
                    }
                    Certificate[] c3 = sslSession.getPeerCertificates();
                    for (Certificate c1One : c3) {
                        System.out.println("getPeerCertificates : " + c1One.toString());
                    }
                    clientCertValid = true;
                } catch (Exception cex) {
                }
                if (clientCertValid) {
                    // Write data
                    printWriter.print("HTTP/1.1 200 OK\r\nServer: johnserver/1.0.8.18\r\nContent-Length: 3\r\n\r\nok\n");
                    printWriter.flush();
                } else {
                    // Write data
                    printWriter.print("HTTP/1.1 200 FAILED\r\nServer: johnserver/1.0.8.18\r\nContent-Length: 6\r\n\r\nFAILED\n");
                    printWriter.flush();
                }
                sslSocket.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
}
    
     证书链制作
    
   
--也可以使用其他证书管理工具(keytool、openssl等)
    
     先下载安装xca工具,地址是
     
      
       http://xca.hohnstaedt.de/
      
     
    
   
    
     先用xca创建一本ca证书,
    
   
    
     本次说明使用的版本为2.1.2
    
   
    
     运行C:\Aprograms\Agreen\xca-portable-2.1.2\xca-portable-2.1.2\xca.exe
    
   
    
     后打开的界面
    
   
     
   
    
     依次File– New DataBase,
    
   
     
   
    
     选择xdb文件保存路径,输入文件名,点击保存,
    
   
     
   
    
     再输入密码(演示密码123456)
    
   
     
   
    
     点击OK确定后,回到主界面
    
   
     
   
    
     切换到Certificates页面,点击New Certificate
    
   
     
   
    
     创建根证书,
    
   
    
     在source栏,签名算法选择SHA 512,证书模版选择默认CA,再点击Apply all
    
   
    
     修改任何内容记得保存
    
   
     
   
    
     在extensions栏,type选择
    
    
     Certificate Authority
    
   
    
     可选
    
    
     —time range
    
    
     有效期设置
    
    
     10
    
    
     年后点击
    
    
     apply
    
   
     
   
    
     Subject
    
    
     栏,填好各个字段,都可以随便填
    
   
     
   
    
     点击Generate a new key生产私钥
    
   
     
   
不修改内容,点击Create
     
   
然后点击新建证书的OK’
     
   
    
     CA
    
    
     证书做好了,有效期默认10年
    
   
     
   
    
     根证书导出成只包含公钥的证书格式,这本根证书就是放在网站上供用户下载安装,或主动安装到客户机器中的
    
   
选择创建好的根证书,点击导出export
     
   
Filename可以选择导出文件路径
     
   
点击OK
     
   
    
     制作服务器证书、客户端证书和制作CA证书差不多,只有两个地方不一样:
    
   
    
     1
    
    
     选择已经制作好的根CA,然后点击New Certificate1
    
   
     
   
2
    
     签名时,选择使用根证书,这里是seeme根证书进行签名颁发,然后证书模版选择服务器HTTPS SERver(制作客户端证书就选择HTTPS_client),其他都和制作根证书一样,然后点击Apply all(这个一定不能忘),然后再切到Subject、Extension页面填写相应的东西就OK了
    
   
    
     Server
    
    
     证书
    
   
     
   
Extensions不修改
     
   
Subject填入信息后点击generate生成证书
     
   
     
   
点击create
     
   
点击新建证书的OK
Server证书创建成功后的展示
     
   
     
   
    
     再将服务器证书导出来,选择p12格式
    
   
     
   
输入server p12导出的密码
(演示的密码112233)
新增https client证书
     
   
     
   
     
   
     
   
点击右下角的generate
     
   
点击create
     
   
点击OK
     
   
     
   
演示的密码111222
所有的证书文件
     
   
     
   
     
   
     
   
     
   
    
     演示的证书passwords密码
    
   
    
     Xdb证书容器123456
    
   
    
     服务器私钥123123
    
   
    
     客户端私钥111222
    
   
    
     
     
     个人客户端证书导入注意
    
   
修改本地客户端证书(删除或者重新导入)
     
   
千万记得点击清除SSL状态
     
   
    
     tomcat配置准备
    
   
作为双向认证支持的web容器,用作java实现的客户端和服务端交叉验证双向认证有效性
    
     Tomcat9下载
    
   
    
     https://tomcat.apache.org/download-90.cgi
    
   
导出的服务器p12证书放在tomcat的conf/sslcerts目录下,(当然也可以使用绝对路径)
    
     修改默认配置,打开https,设置单向认证
    
   
Conf/server.xml
Service节点新增
<Connector port=”8443″ protocol=”org.apache.coyote.http11.Http11NioProtocol”
maxThreads=”150″ SSLEnabled=”true” secure=”true” slProtocol=”TLS”>
<SSLHostConfig>
<Certificate certificateKeystoreFile=”conf/sslcerts/server.p12″ certificateKeystoreType=”PKCS12″ certificateKeystorePassword=”123123″ />
</SSLHostConfig>
</Connector>
Conf/web.xml
welcome-file-list节点后新增
<login-config>
<!– Authorization setting for SSL –>
<auth-method>CLIENT-CERT</auth-method>
<realm-name>Client Cert Users-only Area</realm-name>
</login-config>
<security-constraint>
<!– Authorization setting for SSL –>
<web-resource-collection >
<web-resource-name >SSL</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
    
     安装CA根证书
    
   
双击root.crt
点击安装证书
     
   
     
   
选择本地计算机
     
   
点下一步
     
   
选择 将所有的证书都放入下列存储
     
   
点击浏览选择受信任的根证书机构
点击确定
     
   
点击下一步直到完成
    
     验证单向认证tomcat
    
   
    
     启动tomcat
    
   
    
     打开地址https://localhost:8443/
    
   
点击证书查看按钮(IE 为一个锁的形状)
     
   
     
   
点击查看证书
     
   
点击证书路径
     
   
    显示
    
     
      
       
        该证书没有问题
       
      
     
    
    就是正常的
   
    
     配置双向认证tomcat
    
   
    
     配置需求说明
    
   
本次使用的tomcat9
    
     服务端
    
   
在原有服务的私钥证书配置好的基础上,新增证书容器(包含客户端证书的根证书,能认证客户端公钥有效性的)
    
     客户端
    
   
在原有的(安装服务端证书的根证书后)认证服务端证书的同时,提供客户端公钥证书给服务端ssl请求
    
     Tomcat配置修改
    
   
在新增的connector中
    
     <Connector protocol=”org.apache.coyote.http11.Http11NioProtocol”
    
   
    
     sslImplementationName=”org.apache.tomcat.util.net.jsse.JSSEImplementation”
    
   
    
     port=”8443″ maxThreads=”200″
    
   
    
     scheme=”https” secure=”true” SSLEnabled=”true”
    
   
    
     keystoreFile=”conf/sslcerts/localhost.p12″ keystorePass=”123123″
    
   
    
     truststoreFile=”conf/sslcerts/trustme.jks” truststorePass=”1q2w3e”
    
   
    
     clientAuth=”true” sslProtocol=”TLS” />
    
   
重启tomcat 后
    
     双向验证服务端检查
    
   
打开IE(显示不明确),chrome(本地暂无客户端私钥,客户端证书暂时不提供)
     
   
    提示
    
     ERR_BAD_SSL_CLIENT_AUTH_CERT
    
    
     表示当前浏览器访问
    
    
     ssl
    
    
     核验客户端证书失败
    
   
    
     双向认证客户端检查
    
   
    
     导入客户端证书到本机证书内容
    
   
IE –工具—
     
   
打开internet选项
     
   
点击内容栏
     
   
点击证书
     
   
点击导入证书
     
   
点击下一步
     
   
点击浏览选择客户端私钥文件
     
   
点击下一步
     
   
输入客户端私钥密码
     
   
勾选启用强私钥保护
     
   
点击下一步直到导入成功,
     
   
个人类的证书列表里面会多出刚导入的客户端证书
     
   
     
   
    
     刷新浏览器
    
   
     
   
    
     选择证书
    
   
     
   
选择后续所有的允许
最好能成功打开页面
    
     注意双向认证权限读取申请
    
   
浏览器针对同一地址的双向认证才会弹出证书选择框
后续出现的安全要点允许
    有需要重复操作的请按照
    
     
      
       个人客户端证书导入注意
      
     
    
   
     
   
本文的java demo 工程地址
    
     https://github.com/enderwsp/sslDemo
    
   
工程内包括已经生成的服务器证书 客户端证书 根证书
本来是有一个ngnix的web服务版本做交叉验证的,但没有能和xca证书管理工具很好的搭配使用(不能导出key文件证书,需要openssl命令)
 
