免费 CA 证书安装配置与背后原理浅析
背景
现在线上大多使用HTTPS对外提供服务,然而由于安全考虑,线上证书通常无法在开发测试区使用,为了最大程度减小测试与线上环境差异,需要为测试区单独申请并配置证书
免费证书兼容性也越来越好,安装配置也越来越简单,预算有限的小网站可以使用免费证书
准备
一个公网IP及80/443两个端口(80用来做域名验证)
一个域名,并配置了域名解析,解析到该公网IP
一台内网机器,并且将该公网IP映射到该内网IP
证书申请
在官网letsencrypt.org上申请证书,按说明使用shell操作即可
或者可以通过页面工具生成
申请过程中会要求验证你对该域名有权限,一般要求上传一文件到服务器,并通过外网下载
只要公网开通,并拥有内网机器操作权限,可以很容易按要求进行验证
申请后会获得三个文件:
- certificate.crt
:数字证书,CA用自己的私钥对我们申请的公钥和个人信息进行签名后形成的一个数字文件,和公钥一样是公开的,可以点击浏览器里的小锁标志查看(可以点击小锁导出其对应的cer文件)
- private.key
:网站私钥,用来对信息进行加密
- ca_bundle.crt
:存储了该CA颁发证的根证书交叉文件,用来表明本网站和CA之间的证书链关系
需要将证书和bundle文件合并:
awk 1 certificate.crt ca_bundle.crt > xxx.crt
certificate.crt和ca_bundle.crt虽然看起来就是一段随机数据,但里面包含了很多信息,可以通过
openssl x509 -text -in certificate.crt -noout
来查看
Nginx配置
在映射的内网服务器上安装对应中间件,比如Nginx
在Nginx的配置文件的server区块添加SSL端口监听,如
listen 443 ssl
在Nginx的配置文件的server区块添加SSL证书配置:
测试配置文件是否有语法错误:
nginx -t
配置文件正确的话,重启nginx:
nginx -s reload
通过浏览器访问域名,确定SSL证书正常工作
通过SSL测试工具,确定是否有配置遗漏
可以通过命令查看该CA的证书链:
openssl s_client -connect www.xxx.com:443
。下面结果表示xxx的证书由Let's Encrypt颁发,而Let's Encrypt的证书由Digital Signature Trust交叉信任,任何信任Digital Signature Trust的客户端,也应该信任我们网站
证书兼容性
除了安全,CA证书最重要的就是兼容性,也就是客户端的trust store里是否内置了该CA的根证书(相当于CA公钥),比如上面提到的
DST Root CA X3
。浏览器厂商、操作系统、Java等软件通常会内置流行CA的根证书比较有名的CA厂商有VeriSign、GeoTrust、Digicert,letsencrypt自己的根CA和交叉签名厂商DST流行度一般,但浏览器、iOS、Android等基本兼容,不过JDK是从1.8.0_101以后的版本才开始兼容该CA,具体可参考JDK 1.8.0_101的Release Notes,其他软件可查看[官方兼容性说明](https://letsencrypt.org/docs/certificate-compatibility/)
Our intermediate is signed by ISRG Root X1. However, since we are a very new certificate authority, ISRG Root X1 is not yet trusted in most browsers. In order to be broadly trusted right away, our intermediate is also cross-signed by another certificate authority, IdenTrust, whose root is already trusted in all major browsers. Specifically, IdenTrust has cross-signed our intermediate using their “DST Root CA X3”
出现兼容性问题后,一般可以通过手工将证书导入客户端的trust store来解决
实现原理
申请SSL证书后,网站会获得一个私钥和一个公钥
同时,CA机构会用自己的私钥对网站公钥和网站信息进行加密生成一个证书,这个证书就是网站的数字证书,也就是浏览器地址栏上看到的证书
在访问网站时,浏览器通过自身内置的CA的根公钥来解密这个数字证书,获取该网站的公钥,由于CA根证书是软件厂商(OS、客户端或浏览器厂商)发行软件时内置或用户手工导入的,因此解密出来的公钥能表明的确是该网站所有、且的确是该CA签发
其实就是一个基于非对称算法(如RSA等)的身份验证过程
身份验证通过后,客户端就拿到了网站的公钥,可以进行加密通话了,不过由于性能及安全性等原因,现在通用的加密方案都只使用非对称算法进行身份验证和主秘钥交换,然后通过主密钥进行对称加密,这些不同组合的加密方案被称为加密套件(Cipher Suite)
TLS握手时客户端和服务器端会对双方共同支持的加密套件进行协商,比如chrome80+win10环境下请求www.baidu.com,可以看到客户端通过clienthello告知服务端本地支持17个加密套件,服务端根据本身支持情况选择了【TLSECDHERSAWITH_AES_256GCMSHA384】,并通过server_hello响应通知了客户端,该加密套件表示双方使用DH算法进行秘钥交换、RSA进行身份验证、AES来加密通信、SHA384来做MAC,GCM表示一种加密模式,用来增强AES算法
证书类型和术语
crt文件:certificate的缩写,即证书,常见于类unix系统
key文件:网站私钥
cer文件:也是certificate的缩写,常见于windows系统
der文件:还是证书,二进制格式的
csr文件:Certificate Signing Request文件,用来自动向CA请求数字证书的
pem文件:文本格式的证书
p12文件:网站证书和网站私钥合并后的文件,为PKCS格式
jks文件:和p12相同,只不过是Java专用格式
X509:公钥证书规范
Java相关
概念解释
- trustStore:trustStore是保存了各大CA根证书的文件,jre自带了trustStore,在$JAVA_HOME/jre/lib/security/cacerts
里,Java发起HTTPS请求时,会读取cacerts文件里的根证书。修改该文件需要密码,默认为changeit
- keyStore:keyStore是对外提供SSL服务的应用存储网站证书和网站私钥的文件,Java9之前keyStore默认格式为JKS,之后为更通用的PKCS,因此使用KeyStore.getDefaultType()
方法时要注意;另,一般而言,我们会把证书前置到负载节点,直接提供证书和私钥,而不合并为Java KeyStore文件放到Tomcat里
- trustStore和keyStore区分并不严格,都使用KeyStore类来表示,只是功能不一样,trustStore证书是用来作为客户端来验证TLS服务的,而keyStore是作为服务端提供TLS服务的,因此存储的内容也不一样;很多Java文档也把cacerts文件叫做Java keyStore文件
keytool是jre提供的用来操纵证书和秘钥的工具
- 查看cacerts文件 : keytool -list -keystore $JAVA_HOME/lib/security/cacerts
- 证书导入:keytool -import -file xxx.cer -keystore cacerts -alias paypre
- 证书生成
JSSE
- Java里的SSL验证是通过Java Security Socket Extension框架来实现的,核心接口是X509TrustManager
- trustManager会先通过属性javax.net.ssl.trustStore
来加载指定的keyStore文件
- 如果加载不成功,会加载lib/security/jssecacerts
文件
- 还不成功,才会加载jre自带的lib/security/cacerts
文件
- 因此我们可以通过指定属性、提供jssecacerts文件或覆盖cacerts文件三种方式来添加自己的根证书
TLS版本与Java版本
为了解决隐私和安全性问题,Netscape在90年代初推出了SSL协议,然而SSL1.0和SSL2.0包含大量漏洞,并没有被使用,直到96年发布的SSL3.0才得到推广
之后标准组织IETF开始负责SSL规范制定,并改名为TLS,并推出TLS1.0、TLS1.1和TLS1.2
因安全性原因,TLS并不鼓励支持老版本,现在主流软件一般支持TLS1.1和TLS1.2,部分支持TLS1.0,SSL基本不支持了
各个Java版本对TLS支持情况
| Java版本 |默认支持TLS版本 |支持的其他TLS版本 |
| ------- |-------- | ------- |
| Java 6 | TLS1.0 | TLS1.1 |
| Java 7 | TLS1.0 | TLS1.1、TLS1.2 |
| Java 8 | TLS1.2 | TLS1.0、TLS1.1 |
Java默认支持的TLS版本最重要,比如Java7在和对端建立连接发送
ClientHello
信息时,会告知对方自己要使用TLS1.0建立连接,如果对方不支持TLS1.0或更低版本,就会报错,即使双方都支持TLS1.1或TLS1.2如果希望Java7告知对端自己支持TLS1.1等来建立连接,可以配置属性
System.setProperty(“https.protocols”, “TLSv1.2,TLSv1.1,TLSv1”)
或者命令行里指定-Dhttps.protocols=TLSv1.2,TLSv1.1,TLSv1
,此时,即使对端只支持TLS1.2,也能正常建立连接TLS大体保持向前兼容,比如Java8客户端在和对方建立连接发送
ClientHello
信息时,会告知对方自己要使用TLS1.2建立连接,但如果对方不支持TLS1.2,会在ServerHello
告知使用老版本TLS通信,如果Java8客户端支持并同意,连接也会建立,而非抛出异常也就是说,Server端会认为
ClientHello
里指定的默认TLS版本是Client能支持的最高版本,如果Server的最低版本高于Client的最高版本,Server理所当然的认为连接建立不起来,因此默认TLS版本才这么重要,这不光对Java如此,对其他应用一样
TLS与操作系统
每个操作系统都内置了各大CA的根证书,Linux发行版的CA证书基本是从Mozilla维护的根证书库里获取的,如CentOS放置在
/etc/pki/tls/certs/ca-bundle.crt
,可以通过openssl version -a
看到该目录下还有一个文件
ca-bundle.trust.crt
,这个为EV的根证书,一般我们用不到
问题一览
使用低版本Java直接请求域名,出现
SunCertPathBuilderException: unable to find valid certification path to requested target
错误?
手工将证书导出(注意操作系统),添加到Java KeyStore里
- 浏览器上点击域名锁头-证书-详细信息-复制到文件导出为cer文件(Windows系统)
- 复制到Java KeyStore:keytool -import -file xxx.cer -keystore cacerts -alias paypre
- 查看KeyStore文件cacerts,确定已添加:keytool -list -keystore cacerts
如何在同一个端口里处理HTTP和HTTPS请求?
同一个端口不能同时处理https和http请求
如果内网机器使用同一个端口来监听HTTP和HTTPS请求时,就需要在SSL端口处理非SSL请求,或反过来
当配置了
listen 8080 ssl
后,如果HTTP请求也通过8080端口到达,nginx会产生一个内部错误码497nginx的错误处理规定,可以针对特定错误码指定重定向url,因此,如下配置即可在同一个端口里处理HTTP和HTTPS请求:
error_page 497 =307 https://paypre.xxx.com$request_uri;
重定向码之所以明确指定用307,是因为很多浏览器会把302当做303处理,即POST方法的重定向自动会变成GET,而307明确不能改变method类型
maven在build复制resource下的jks等证书文件时,可能会损坏证书,原因
参考资料
版权声明: 本文为 InfoQ 作者【陈德伟】的原创文章。
原文链接:【http://xie.infoq.cn/article/15c2a1514dae2db6f6c560e10】。文章转载请联系作者。
评论