关于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命令)