半年前老板让我研究一下CA认证,说可能要用到,预留一个证书登陆功能,当时看懂之后就简单demo不了了之了。最近突然要用了,一下懵了。这次做个总结,留个印。
一、 背景
CA证书正式开发是需要购买的,开发内测可以用生成数字证书的工具,有openssl、keytool等。
openssl:SSL 密码库工具,其提供了一个通用、健壮、功能完备的工具套件,用以支持SSL/TLS 协议的实现。
keytool:JDK里面内置的一个数字证书生产工具,只能生成自签名的数字证书。且不支持导出私钥。所有的数字证书是以一条一条(采用别名区别)的形式存入证书库的中,证书库中的每个证书包含该条证书的私钥,公钥和对应的数字证书的信息。
https:本地是配置tomcat,使https访问生效
(一)什么是HTTPS?
在说HTTPS之前先说说什么是HTTP,HTTP就是我们平时浏览网页时候使用的一种协议。HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全。为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。SSL目前的版本是3.0,被IETF(Internet Engineering Task Force)定义在RFC 6101中,之后IETF对SSL 3.0进行了升级,于是出现了TLS(Transport Layer Security) 1.0,定义在RFC 2246。实际上我们现在的HTTPS都是用的TLS协议,但是由于SSL出现的时间比较早,并且依旧被现在浏览器所支持,因此SSL依然是HTTPS的代名词,但无论是TLS还是SSL都是上个世纪的事情,SSL最后一个版本是3.0,今后TLS将会继承SSL优良血统继续为我们进行加密服务。目前TLS的版本是1.2,定义在RFC 5246中,暂时还没有被广泛的使用。
(二)Https的工作原理
HTTPS在传输数据之前需要客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息。TLS/SSL协议不仅仅是一套加密传输的协议,更是一件经过艺术家精心设计的艺术品,TLS/SSL中使用了非对称加密,对称加密以及HASH算法。握手过程的简单描述如下:
- 浏览器将自己支持的一套加密规则发送给网站。
- 网站从中选出一组加密算法与HASH算法,并将自己的身份信息以证书的形式发回给浏览器。证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息。
- 获得网站证书之后浏览器要做以下工作:
- 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致等),如果证书受信任,则浏览器栏里面会显示一个小锁头,否则会给出证书不受信的提示。
- 如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密。
- 使用约定好的HASH计算握手消息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站。
- 网站接收浏览器发来的数据之后要做以下的操作:
- 使用自己的私钥将信息解密取出密码,使用密码解密浏览器发来的握手消息,并验证HASH是否与浏览器发来的一致。
- 使用密码加密一段握手消息,发送给浏览器。
- 浏览器解密并计算握手消息的HASH,如果与服务端发来的HASH一致,此时握手过程结束,之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密。
这里浏览器与网站互相发送加密的握手消息并验证,目的是为了保证双方都获得了一致的密码,并且可以正常的加密解密数据,为后续真正数据的传输做一次测试。另外,HTTPS一般使用的加密与HASH算法如下:
- 非对称加密算法:RSA,DSA/DSS
- 对称加密算法:AES,RC4,3DES
- HASH算法:MD5,SHA1,SHA256
其中非对称加密算法用于在握手过程中加密生成的密码,对称加密算法用于对真正传输的数据进行加密,而HASH算法用于验证数据的完整性。由于浏览器生成的密码是整个数据加密的关键,因此在传输的时候使用了非对称加密算法对其加密。非对称加密算法会生成公钥和私钥,公钥只能用于加密数据,因此可以随意传输,而网站的私钥用于对数据进行解密,所以网站都会非常小心的保管自己的私钥,防止泄漏。TLS握手过程中如果有任何错误,都会使加密连接断开,从而阻止了隐私信息的传输。
二、 认证
(一)几个文件格式
.p12(PKCS #12)
我们的每一个证书都可以生成一个.p12文件,这个文件是一个加密的文件,只要知道其密码,就可以供给所有的系统设备使用,使设备不需要在重新申请开发和发布证书,就能使用。
.keyStore(密钥库)
keystore中一般保存的是我们的私钥,用来加解密或者为别人做签名用。
.truststore(信任库)
truststore中保存的是一些可信任的证书,主要是java在代码中访问某个https的时候对被访问者进行认证的,以确保其是可信任的。
truststore是必须的,如果我们没有显式的指定,那么java会默认指定为$JAVA_HOME/lib/security/cacerts 这个文件。
各种数字证书区别
(二)利用tomcat服务器配置https双向认证
这里使用JDK自带的信任CA证书 keytool工具
学习认证的过程,发现网上有很多种做法,而且好像都能握手,经过各种五花八门的搜索查证,总结了以下两种思路的理解:
认证方式一
这种方式大致理解是:
模拟一个CA认证中心库 root.p12,
服务端:将证书server.cer由 root认证
客户端:将证书client.cer由server信任(及放入server.p12库中)
第一步:生成根证书颁发机构的密钥库 root.p12
keytool -genkeypair -v -keystore root.p12 -storetype pkcs12 -storepass 123456 -alias rootCA -keyalg RSA -keysize 2048 -validity 36500
1 密钥库的密码和密钥密码都设置成123456(JKS格式的话,需要在生成密钥库时storepass和keypass相同)
2 如果不加keyalg ,默认是DSA算法生成
3 “您的名字与姓氏是什么?
”这是必填项,并且必须是TOMCAT部署主机的域名或者IP[如:gbcom.com或存10.1.25.251](就是你将来要在浏览器中输入的访问
地址),否则浏览器会弹出警告窗口,提示用户证书与所在域不匹配。这里为了直观,我测试填写的是“www.aliguma.com”。
第二步:生成服务器密钥库 server.p12
keytool -genkeypair -v -keystore server.p12 -storetype pkcs12 -storepass 123456 -alias server -keyalg RSA -keysize 2048 -validity 36500
第三步:服务端密钥库生成证书请求
keytool -certreq -keystore server.p12 -storepass 123456 -alias server -file server.csr
第四步:建立CA密钥库与服务端的信任
1:CA密钥库导出CA根证书:
keytool -exportcert -file root.cer -keystore root.p12 -storepass 123456 -alias rootCA
2:服务器密钥库信任CA根证书:
keytool -importcert -file root.cer -keystore server.p12 -storepass 123456 -alias rootCA
第五步:使CA密钥库认证服务端的请求证书.csr,并导出认证后的新证书(回复证书)
keytool -gencert -v -infile server.csr -outfile server.cer -keystore root.p12 -storepass 123456 -alias rootCA
第六步:导入回复后的证书(证书请求在回复后是一个证书链)
keytool -importcert -file server.cer -keystore server.p12 -storepass 123456 -alias server
第七步:生成客户端密钥库 client.p12
keytool -genkeypair -v -keystore client.p12 -storetype pkcs12 -storepass 654321 -alias client -keyalg RSA -keysize 2048 -validity 36500
第八步:导出客户端证书 client.cer
keytool -exportcert -v -file client.cer -keystore client.p12 -storepass 654321 -alias client
第九步:将客户端证书导入服务端密钥库信任证书列表
Keytool -importcert -v -file client.cer -keystore server.p12 -storepass 123456 -alias client
或者直接另存到新的密钥信任库(这里的区别在于tomcat的server.xml文件配置的truststoreFile的路径)
Keytool -importcert -v -file client.cer -alias client -keystore trustore.p12 -storepass 123456 -storetype pkcs12
图解
查看证书列表
keytool -list -v -keystore server.p12 -storepass 123456
Tomcat的配置
1.修改域名
2.server.xml
<Connector connectionTimeout="20000" maxHttpHeaderSize="8999"
port="80" protocol="HTTP/1.1" redirectPort="443"/>
<Connector port="443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="true"
sslProtocol="TLS"
keystoreFile="D:\home\root\server.p12" keystorePass="123456"
truststoreFile="D:\home\root\server.p12" truststorePass="123456"/>
<!--
clienlAuth:设置是否双向验证,默认为false,设置为true代表双向验证
keystoreFile:存储加密证书的密钥库
keystorePass:密钥库访问密码
truststoreFile:保存信任证书的密钥库。这里只需要证书(公钥)即可,而我们服务端密钥库将客户端证书添加信任的同时也保存了他,所以这里可以直接设置服务端密钥库地址。假如有客户端的密钥库的话,或者新建了一个密钥库用以专门保存信任证书的话,也可以写它的地址,虽然我们只用它的公钥
truststorePass:保存信任证书密钥库的访问密码
-->
如果配置的是双向认证,而没有配置truststore,访问服务端时会出现访问等待转圈圈,一直等待服务端响应。
3.web.xml
<!--自动跳转https -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Automatic Forward to HTTPS/SSL</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
(一)直观安装测试
java代码测试在后面
1.安装CA根证书root.cer(CA密钥库公钥)到 “可信任的根证书颁发机构”
2.双击安装客户端密钥库到 “个人”(注意,是密钥库而不是证书,因为在通信的时候需要用到私钥)
3.打开https://www.aliguma.com
如果服务端验证客户端证书时,发现客户端没有证书,然后就断开了握手连接,访问失败。
访问时chrome不可以,但是360会出现下面弹窗,导入证书后就可以访问了
(这里有个疑惑,既然已经将root.p12放在windows中了,它充当的就是可信的跟证书,服务端验证应该是不会出现
‘https’ 或 ‘X!’ ,可能是浏览器自己的机制,依然并不认可,下图是有无root,访问服务端的对比。)
关于这点网上有自签名证书报错的解决方案,但作者用的是openssl。还有说是需要添加主题备用名称,在生成服务端密钥库的时候加上 -ext san = dns:www.example.com,但是我试着加上www.aliguma.com,依然不行,暂时无解了。
认证方式二
与方式一相比,上诉第七步至第九步 关于客户端的步骤与服务端的一样,客户端同样需要与CA密钥库建立信任(参考第二步至第六步)
这种认证方式的场景就好像演唱会,会场(server)对购票者(client)能否入场的认证,他不在乎你是谁,你不认识他,他也不认识你,只要你有主办方(CA密钥库 root)认证的门票,就可以通过,会场与购票者直接的认证关系建立在主办方,他们相互没有联系。当然,会场(server)也是主办方认证过,才能举办。
实际场景应该就是大公司面向散户的一种方式,公司密钥库里不可能有每个散户的认证,这也不现实,只要散户是被权威认证是可信任的,那就可以访问。
方式二经测试,同样也可以访问。
关于网上说的认证方式的第三种,没有根证书的参与,只要是客户端和服务端之间,将各自的证书放入对方密钥库,建立相互信任的关系就可以访问,这种方式网上比较普遍,这里就不介绍了。
(二)Java中实现SSL通信的测试
相关类之间的关系
(注:图片引自《 Java™ Secure Socket Extension (JSSE) Reference Guide 》)
public class SSLCliAuth {
private static final String SERVER_KEYSTORE_TYPE = "PKCS12";
private static final String SERVER_KEYSTORE_PATH = "D:\\home\\root\\server.p12";
private static final String SERVER_KEYSTORE_PASS = "123456";
private static final String CLIENT_KEYSTORE_TYPE = "PKCS12";
private static final String CLIENT_KEYSTORE_PATH = "D:\\home\\root\\client.p12";
private static final String CLIENT_KEYSTORE_PASS = "654321";
public static void main(String[] args) throws Exception {
requestTimestamp();
}
public final static void requestTimestamp() throws Exception {
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(
createSslCustomContext(),
new String[]{"TLSv1"},
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(csf).build()) {
HttpPost req = new HttpPost("https://www.aliguma.com");
System.out.println("Executing request " + req.getRequestLine());
CloseableHttpResponse res = httpclient.execute(req);
try (CloseableHttpResponse response = httpclient.execute(req)) {
HttpEntity entity = response.getEntity();
System.out.println(response.getStatusLine());
} finally {
res.close();
}
}
}
public static SSLContext createSslCustomContext() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException {
// 服务端
KeyStore sks = KeyStore.getInstance(SERVER_KEYSTORE_TYPE);
sks.load(new FileInputStream(SERVER_KEYSTORE_PATH), SERVER_KEYSTORE_PASS.toCharArray());
// 客户端
KeyStore cks = KeyStore.getInstance(CLIENT_KEYSTORE_TYPE);
cks.load(new FileInputStream(CLIENT_KEYSTORE_PATH), CLIENT_KEYSTORE_PASS.toCharArray());
SSLContext sslcontext =SSLContexts.custom()
//忽略掉对服务器端证书的校验
/*.loadTrustMaterial(new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
})*/
.loadTrustMaterial(sks, new TrustSelfSignedStrategy()) // 加载服务端提供的truststore
.loadKeyMaterial(cks, CLIENT_KEYSTORE_PASS.toCharArray()) // 加载客户端
.build();
return sslcontext;
}
}
输出结果,
成功
失败(服务端验证客户端证书时,发现客户端没有证书,然后就断开了握手连接)
以上就是对CA认证的理解,很多地方借鉴网友的,自签名证书本身与实际的权威证书存在区别,各个浏览器的响应差强人意,如果我的理解有偏差,请多多指点,互相学习。
相关参考与学习:
Java 安全套接字编程以及 keytool 使用最佳实践
SSL证书配置常见问题
Tomcat启用HTTPS协议配置过程
Keytool工具生成SSL证书以及在Java中实现SSL
使用Tomcat 9验证Https单向认证和双向认证
ssl代码
如何使用HttpClient来发送带客户端证书的请求,以及如何忽略掉对服务器端证书的校验