Open xizhibei opened 3 years ago
在很久之前,我在 划一划 HTTPS 以及 SSL/TLS 的重要知识点 提到过客户端 HTTPS 证书,之后就没后续了,不过目前还是遇到了接口问题,不得不用上了。
接下来会给大家说清几个概念:HTTPS 单向认证、HTTPS 双向认证、中间人攻击。
在这两种认证场景中,主要的区别在于服务器是不是需要客户端提供证书验证,我们,在进行 TCP 三次握手成功后,就开始进行 SSL 阶段的握手了了。
SSL 握手阶段可以参考如下,此图盗自 Ssl handshake with two way authentication with certificates 。这张图里面的大致阶段是对的,只是并不是很详细,更详细的请参考 Transport Layer Security 。
正如如上图所示,这个过程中,主要区别就在于第三阶段1:
如果把第三阶段省略掉,那就变成单向认证了,所以担心加密过程开销的同学们可以稍稍省点心了,因为整个过程中,就是 SSL 握手阶段的开销会略微加大,而实际通信过程中的开销还是与单向认证一样。
所谓中间人攻击,就是在双方握手阶段,由于没有用 CA 对证书进行验证造成的问题:比如你如果不在意浏览器的警告,或者被骗安装了攻击者提供的 CA 证书。攻击者可以在你与服务器建立 SSL 通信的时候,先用假的证书与你建立 SSL 通信,然后再与服务器建立 SSL 通信,这个过程中,攻击者就能拿到这个过程中所有的明文消息。
用一个非常简单的例子来说明,就是 HTTPS 抓包,比如现在你需要抓手机上的 HTTPS 请求,那么你就需要在手机上安装抓包工具提供的 CA 证书,然后配置好代理后,就可以抓到 HTTPS 的明文请求了。
到目前,我们知道了 HTTPS 双向认证是怎么一回事,那么它能解决我们的什么问题呢?防止有人冒充我们的客户端来请求服务器的私有资源,那么我们可以直接去验证客户端的身份,就能在很大程度上解决问题。
下面自问自答,来帮大家理清楚思路:
相信这样的场景很常见,为了保护接口,直接跟客户端约定一个密钥,然后大家根据这个密钥来进行加密通信,这种方案在我看来很容易破解,而且一旦破解拿到密钥,你很难短时间里面通过更新客户端来更换密钥。
这种方式,做的好的能够使用公开经得起验证的加密算法,而做的差的会用私有加密算法(多个公开算法叠加套用不算私有)。这在一定程度上能够阻挡破解者,只是私有加密算法出问题的概率在遇到高价值资源的时候,就会变得非常大,简而言之,就如闭源与开源一般。
另外,还有个很尴尬的地方在于,在开发人员进行接口调试的时候,一大段密文信息将会让调试非常困难。于是,所谓的的防止破解,更多的时候都在恶心开发者自己了。
用 HTTPS 证书就不会有这个问题了,在自己电脑安装完成证书后,就能在浏览器中明文调试了。
理论上,跟 HTTPS 单向的认证区别就在于多了一步验证客户端证书的过程,因此会多出 SSL 握手时的开销,具体我还没有压力测试过,但是我相信玩过 kubernetes 的应该会对此有个概念。
如果你预计会有比较大的流量,不放心,那就建议先找几台机器进行测试再决定也不迟。
确实如此,如果客户端会流落在他人手里,那么在不加一层保护的前提下,确实有可能会泄露,只是你有两种方案可以一起使用来保护证书:
strings
我的理由主要有两点:
大多数没有选择客户端证书的,我认为是因为对它不够了解,或者说它的强大显得过于简单,导致很多人都没有什么安全感,尤其是领导没有什么安全感,但是请大家不妨想想现在有谁能够简单破解 HTTPS 请求内容呢?基于 RSA 加密的通信,是我们现代互联网的安全基础,没有谁能够轻易破解。
目前大部分方案都是拿 OpenSSL + Nginx 来举例的(随便搜索都是一大堆),那我就把 OpenSSL 换成更简单好用的 CFSSL 。
多种方式可选,比如最简单的就是直接去下载已经编译好的工具:cfssl/releases 。
我目前使用的是 v1.5.0 版本的,大版本前提下,命令应该不会相差太多。
生成 CA 默认配置:
cfssl print-defaults config > ca-config.json
这里面的内容需要略加修改,下面给出一个我修改的例子,因为今天只需要客户端证书,因此 profiles 里面只留下了 client,signing.default.expiry 表示签发证书的默认过期时间,时间比较短,而另一个 signing.profiles.client.expiry 则比较长,8760h 就表示一年了。
signing.default.expiry
signing.profiles.client.expiry
{ "signing": { "default": { "expiry": "168h" }, "profiles": { "client": { "expiry": "8760h", "usages": [ "signing", "key encipherment", "client auth" ] } } } }
然后是生成 CA 证书申请:
cfssl print-defaults csr > ca-csr.json
也需要根据自己的情况修改,这里需要注意下 key,v1.5.0 下面默认是 ecdsa-256 ,而我改成了 rsa-2048,只是为了说明方便,以及兼容性。
{ "CN": "Awesome Inc", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "CN", "ST": "Guangdong", "L": "Shenzhen", "O": "Awesome Inc", "OU": "Tech Dept" } ] }
然后就可以生成 CA 证书了:
cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
在当前目录下,会生成三个文件:
接下来就开始生产客户端证书,与上面是类似的:
cfssl print-defaults csr > client.json
照例给出 client.json 的修改样例:
{ "CN": "xizhibei", "hosts": [""], "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "CN", "ST": "Guangdong", "L": "Shenzhen", "O": "Awesome Inc", "OU": "Tech Dept" } ] }
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client
命令行中会出现如下的日志:
2021/02/03 15:21:09 [INFO] generate received request 2021/02/03 15:21:09 [INFO] received CSR 2021/02/03 15:21:09 [INFO] generating key: rsa-2048 2021/02/03 15:21:10 [INFO] encoded CSR 2021/02/03 15:21:10 [INFO] signed certificate with serial number 657128885698804019594922156238712961504332210277
然后生成了三个文件:
当证书发布出去后,在有效期之前,它是一直有效的,但是如果我们想让它提前失效(比如泄露的情况下),那么就需要用到撤销证书。
比如,我们要撤销上面刚刚生成的证书,需要先将序列号写入撤销证书序列号列表,然后来生成撤销证书:
echo 657128885698804019594922156238712961504332210277 >> crl-serials.txt cfssl gencrl crl-serials.txt ca.pem ca-key.pem| base64 -D | openssl crl -inform DER -out ca-crl.pem
然后替换已有的撤销证书即可。
好了,到目前为止,证书部分基本上就搞定了,相对 OpenSSL 来说是不是很简单?
将上面生成的 ca.pem 以及 ca-crl.pem 放在 /etc/nginx/certs/client_ca 中,然后在 server 中如下配置(这里假设你已经开启了 HTTPS,并且配置好了服务端的证书):
ca.pem
ca-crl.pem
/etc/nginx/certs/client_ca
ssl_client_certificate /etc/nginx/certs/client_ca/ca.pem; ssl_crl /etc/nginx/certs/client_ca/ca-crl.pem; ssl_verify_client on;
更新 Nginx:
nginx -s reload
这里留给你一个思考题,为什么这里只需要配置一个 CA 证书就可以了?
如果你在配置好 nginx 后,直接用浏览器请求网站,那么你就会看到一个 HTTP 400 错误,告诉你需要提供证书。
本地安装证书很简单,直接鼠标双击证书就能够安装,如果是 Mac,记得在 Keychain 中配置始终信任。
另外,为了方便发送证书给他人使用,建议打包成 pkcs12 格式的证书,还能设置密码。
pkcs12
openssl pkcs12 -export -clcerts \ -CApath . -inkey xizhibei-key.pem -in xizhibei.pem \ -certfile ca.pem -passout pass:strong-password -out xizhibei.p12
@xizhibei
本地安装证书很简单,直接鼠标双击证书就能够安装,如果是 Mac,记得在 Keychain 中配置始终信任。 另外,为了方便发送证书给他人使用,建议打包成 pkcs12 格式的证书,还能设置密码。
mac用户打包完p12证书后,安装一直提示密码输入错误,密码实际上并没有输入错误,请问这是怎么回事呢?通过openssl pkcs12 -info命令查看证书信息也都正常
openssl pkcs12 -info
在很久之前,我在 划一划 HTTPS 以及 SSL/TLS 的重要知识点 提到过客户端 HTTPS 证书,之后就没后续了,不过目前还是遇到了接口问题,不得不用上了。
简介
接下来会给大家说清几个概念:HTTPS 单向认证、HTTPS 双向认证、中间人攻击。
HTTPS 单向认证与双向认证
在这两种认证场景中,主要的区别在于服务器是不是需要客户端提供证书验证,我们,在进行 TCP 三次握手成功后,就开始进行 SSL 阶段的握手了了。
SSL 握手阶段可以参考如下,此图盗自 Ssl handshake with two way authentication with certificates 。这张图里面的大致阶段是对的,只是并不是很详细,更详细的请参考 Transport Layer Security 。
正如如上图所示,这个过程中,主要区别就在于第三阶段1:
如果把第三阶段省略掉,那就变成单向认证了,所以担心加密过程开销的同学们可以稍稍省点心了,因为整个过程中,就是 SSL 握手阶段的开销会略微加大,而实际通信过程中的开销还是与单向认证一样。
中间人攻击
所谓中间人攻击,就是在双方握手阶段,由于没有用 CA 对证书进行验证造成的问题:比如你如果不在意浏览器的警告,或者被骗安装了攻击者提供的 CA 证书。攻击者可以在你与服务器建立 SSL 通信的时候,先用假的证书与你建立 SSL 通信,然后再与服务器建立 SSL 通信,这个过程中,攻击者就能拿到这个过程中所有的明文消息。
用一个非常简单的例子来说明,就是 HTTPS 抓包,比如现在你需要抓手机上的 HTTPS 请求,那么你就需要在手机上安装抓包工具提供的 CA 证书,然后配置好代理后,就可以抓到 HTTPS 的明文请求了。
我们的问题
到目前,我们知道了 HTTPS 双向认证是怎么一回事,那么它能解决我们的什么问题呢?防止有人冒充我们的客户端来请求服务器的私有资源,那么我们可以直接去验证客户端的身份,就能在很大程度上解决问题。
下面自问自答,来帮大家理清楚思路:
为什么不用自己的方式来加密呢?
相信这样的场景很常见,为了保护接口,直接跟客户端约定一个密钥,然后大家根据这个密钥来进行加密通信,这种方案在我看来很容易破解,而且一旦破解拿到密钥,你很难短时间里面通过更新客户端来更换密钥。
这种方式,做的好的能够使用公开经得起验证的加密算法,而做的差的会用私有加密算法(多个公开算法叠加套用不算私有)。这在一定程度上能够阻挡破解者,只是私有加密算法出问题的概率在遇到高价值资源的时候,就会变得非常大,简而言之,就如闭源与开源一般。
另外,还有个很尴尬的地方在于,在开发人员进行接口调试的时候,一大段密文信息将会让调试非常困难。于是,所谓的的防止破解,更多的时候都在恶心开发者自己了。
用 HTTPS 证书就不会有这个问题了,在自己电脑安装完成证书后,就能在浏览器中明文调试了。
会给服务器造成很大压力吗?
理论上,跟 HTTPS 单向的认证区别就在于多了一步验证客户端证书的过程,因此会多出 SSL 握手时的开销,具体我还没有压力测试过,但是我相信玩过 kubernetes 的应该会对此有个概念。
如果你预计会有比较大的流量,不放心,那就建议先找几台机器进行测试再决定也不迟。
客户端证书不是也有泄露的风险吗?
确实如此,如果客户端会流落在他人手里,那么在不加一层保护的前提下,确实有可能会泄露,只是你有两种方案可以一起使用来保护证书:
strings
命令, 或者解压你的安装包就能获取到明文证书。另外,你也可以直接用 pkcs12 格式的证书,记得更换一个更强的密码;为何我对于客户端证书如此推崇?
我的理由主要有两点:
大多数没有选择客户端证书的,我认为是因为对它不够了解,或者说它的强大显得过于简单,导致很多人都没有什么安全感,尤其是领导没有什么安全感,但是请大家不妨想想现在有谁能够简单破解 HTTPS 请求内容呢?基于 RSA 加密的通信,是我们现代互联网的安全基础,没有谁能够轻易破解。
如何实现2、3
目前大部分方案都是拿 OpenSSL + Nginx 来举例的(随便搜索都是一大堆),那我就把 OpenSSL 换成更简单好用的 CFSSL 。
安装
多种方式可选,比如最简单的就是直接去下载已经编译好的工具:cfssl/releases 。
我目前使用的是 v1.5.0 版本的,大版本前提下,命令应该不会相差太多。
CA 证书
生成 CA 默认配置:
这里面的内容需要略加修改,下面给出一个我修改的例子,因为今天只需要客户端证书,因此 profiles 里面只留下了 client,
signing.default.expiry
表示签发证书的默认过期时间,时间比较短,而另一个signing.profiles.client.expiry
则比较长,8760h 就表示一年了。然后是生成 CA 证书申请:
也需要根据自己的情况修改,这里需要注意下 key,v1.5.0 下面默认是 ecdsa-256 ,而我改成了 rsa-2048,只是为了说明方便,以及兼容性。
然后就可以生成 CA 证书了:
在当前目录下,会生成三个文件:
客户端证书
接下来就开始生产客户端证书,与上面是类似的:
照例给出 client.json 的修改样例:
命令行中会出现如下的日志:
然后生成了三个文件:
撤销证书
当证书发布出去后,在有效期之前,它是一直有效的,但是如果我们想让它提前失效(比如泄露的情况下),那么就需要用到撤销证书。
比如,我们要撤销上面刚刚生成的证书,需要先将序列号写入撤销证书序列号列表,然后来生成撤销证书:
然后替换已有的撤销证书即可。
好了,到目前为止,证书部分基本上就搞定了,相对 OpenSSL 来说是不是很简单?
配置 Nginx
将上面生成的
ca.pem
以及ca-crl.pem
放在/etc/nginx/certs/client_ca
中,然后在 server 中如下配置(这里假设你已经开启了 HTTPS,并且配置好了服务端的证书):更新 Nginx:
这里留给你一个思考题,为什么这里只需要配置一个 CA 证书就可以了?
客户端证书的安装
如果你在配置好 nginx 后,直接用浏览器请求网站,那么你就会看到一个 HTTP 400 错误,告诉你需要提供证书。
本地安装证书很简单,直接鼠标双击证书就能够安装,如果是 Mac,记得在 Keychain 中配置始终信任。
另外,为了方便发送证书给他人使用,建议打包成
pkcs12
格式的证书,还能设置密码。Ref