咨询热线

13607947669

新兴职业培训网
您当前的位置:职业证书

Certificate Pinning是如何工作的?

发布时间:2021-10-24  点击量:
更多

Certificate Pinning是什么,有什么用?

Certificate Pinning,或者有叫作SSL Pinning/TLS Pinning的,都是指的同一个东西,中文翻译成证书锁定,最大的作用就是用来抵御针对CA的攻击。在实际当中,它一般被用来阻止man-in-the-middle(中间人攻击)。

说起中间人攻击,可能不是那么直观,但是这一类工具我们可能经常会用到,如Mac平台的Charles和Windows平台的Fiddler。如果一个应用使用了Certificate Pinning技术,那么你使用前边说的工具是无法直接来调试/监控应用的网络流量的。

当应用通过HTTPS握手连接到Fidder/Charles时,应用会检查请求的response的证书,如果发现与预设的不一致,会拒绝后续的网络请求,从而增加应用与服务器的安全通信。

关于破解/越狱系统可以突破上述的限制,则是另外一个话题,这里就不展开了。

为什么要用,我可以不用吗?

没有绝对的安全,用或者不用都是权衡各种利弊,最后的一个妥协的结果。

认为不应该使用的理由是:

一般来说,操作系统自己的trust store就可以信赖了

如果使用,应用需要在证书过期前更新证书,重新发版

认为应该使用的,可能是

万一操作系统被破解,怎么办?就像上边提到的一样

反正我的应用需要经常迭代,没关系

公司的安全部门要求应用里边做Certificate Pinning (有些能自己掌控的就不要依赖被人的意味)

接下里,我们假定经过了各种权衡之后,我们同意后者。那么要怎么做呢?

好的,要怎么实现呢?

在做之前,我们先了解一下我们可以根据什么来Pinning?一般来说,可以直接Pin证书,或者Pin证书的public key。

这里以Android平台为例子,看看我们一般都是怎么做的。

学院派实现 - Pin证书

其实是固定的写法,基本流程就是(其实注释已经很清楚了):

加载证书文件,并使用CertificateFactory生成一个X509Certificate的实例

创建一个KeyStore实例,并把前边的X509Certificate实例加进去,并起一个别名

注意,这里其实是可以加多个证书进去的,但是注意别名不要重复,因为底层实现是使用一个Map存储别名与证书的

创建一个TrustMan**er,并且使用前边的KeyStore实例进行初始化

创建一个SSLContext,并且使用前边的TrustMan**er实例进行初始化

最后,使用SSLContext创建一个SSLSocketFactory实例,并且把它赋值给我们用于https的请求连接对象HttpsURLConnection

特别简单的配置文件实现 - Pin证书/Public Key

在Android 7.0之后,Android支持一种特别方便的实现,只需要在Manifest文件的android:networkSecurityConfig属性加上对应的配置文件即可。

这种方式支持证书文件和hash的public key两种形式。

配置文件长这个样子

OkHttp的实现 - Pin Public Key

Android领域很多和网络相关的Library都支持OkHttp作为底层的网络请求引擎。所以我们可以看看OkHttp里边是怎么实现的。

OkHttp也可以通过sslSocketFactory()来实现第一种的Pinning,不过我们可以通过其专门提供的CertificatePinner更加方便的实现Pinning。

看一下例子:

题外话:publicobject.com其实是OkHttp的作者Jesse Wilson自己的网站。

作者在Google的时候创建了OkHttp,后边去了Square,把它“发扬光大”,后边又被Google拿过去作为Android系统底层网络请求的底层实现了。

那么如何拿到上面所需要的hash值呢?官方给的一个方法是,先填写一个错的hash值,然后根据随后的exception的stack trace mess**e,得到对应的hash值。

其实也可以通过openssl提供的命令直接从der或者pem格式的证书中计算出来,由于命令相对复杂一些,我写了一个简单的脚本封装了一下,支持两种格式。

下载链接是:get sha256 of x509 certificate.sh

Pinning是如何工作的?

那么上述的那些实现底层是如何工作的,怎么样保证Pinning呢?网上其实有比较详细讲解其工作原理的,但是可能偏理论化。这里以OkHttp为例,看一下它是如何实现的。

我分析的版本是4.0.1,其他版本可能略有不同,但是大体流程是一样的。

这里我画了一个简单的调用时序图(有些地方省略了,主要看一下它的调用流程),它可以帮助我们明白OkHttp到底是在什么时候进行Pinning的。

我们可以看到,实在运行到预设的ConnectInterceptor时,进行Pinning的。

这里主要看一下RealConnection.check方法的实现

关于这个图的绘制,我把它的源代码也分享出来,有兴趣的朋友可以试试 Mermaid Live Editor。

这个方法也是整个调用连的核心,在这里OkHttp创建了Socket连接,然后在执行完SSLSocket的握手创建SSLSession,从其里边拿到一个Certificate数组(getPeerCertificates),然后以此计算每个证书的subjectPublicKeyInfo的sha256 hash值,与预设的值做比较,如果有一个符合的,则验证通过,否则,则会抛一个SSLPeerUnverifiedException异常告知开发者。

如果要打破砂锅问到底,那么就需要去看JDK中关于SSLSocket的实现代码了。

Summary

稍微总结一下,我们依次

了解了为什么要做Pinning,即它可以解决什么问题

Pinning也不是完美的,它也有它的弊端,还要根据自己的实际情况来决定是否使用

以Android平台为例,列举了三种常见的实现方法

以OkHttp为例,稍微深入了解了它是如何实现的

关于在其他平台,比如iOS, Flutter可以去网上搜索相关的解决方案。

OkHttp 证书 hash值 实例 底层