Certificate Pinning是什么,有什么用?
Certificate Pinning,或者有叫作SSL Pinning/TLS Pinning的,都是指的同一个东西,中文翻译成证书锁定,最大的作用就是用来抵御针对CA的攻击。在实际当中,它一般被用来阻止man-in-the-middle
(中间人攻击)。
说起中间人攻击,可能不是那么直观,但是这一类工具我们可能经常会用到,如Mac平台的Charles
和Windows平台的Fiddler
。如果一个应用使用了Certificate Pinning技术,那么你使用前边说的工具是无法直接来调试/监控应用的网络流量的。
当应用通过HTTPS握手连接到Fidder/Charles时,应用会检查请求的response的证书,如果发现与预设的不一致,会拒绝后续的网络请求,从而增加应用与服务器的安全通信。
关于root系统可以突破上述的限制,则是另外一个话题,这里不做讨论。
为什么要用,我可以不用吗?
没有绝对的安全,用或者不用都是权衡各种利弊,最后的一个妥协的结果。
认为不应该使用的理由是:
- 一般来说,操作系统自己的trust store就可以信赖了
- 如果使用,应用需要在证书过期前更新证书,重新发版
- …
认为应该使用的,可能是
- 万一操作系统被破解,怎么办?就像上边提到的一样
- 反正我的应用需要经常迭代,没关系
- …
- 公司的安全部门要求应用里边做Certificate Pinning (有些能自己掌控的就不要依赖被人的意味)
接下里,我们假定经过了各种权衡之后,我们同意后者。那么要怎么做呢?
好的,要怎么实现呢?
在做之前,我们先了解一下我们可以根据什么来Pinning?一般来说,可以直接Pin证书,或者Pin证书的public key。
这里以Android
平台为例子,看看我们一般都是怎么做的。
"学院派"实现 - Pin证书
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
// From https://www.washington.edu/itconnect/security/ca/load-der.crt
val caInput: InputStream = BufferedInputStream(FileInputStream("load-der.crt"))
val ca: X509Certificate = caInput.use {
cf.generateCertificate(it) as X509Certificate
}
System.out.println("ca=" + ca.subjectDN)
// Create a KeyStore containing our trusted CAs
val keyStoreType = KeyStore.getDefaultType()
val keyStore = KeyStore.getInstance(keyStoreType).apply {
load(null, null)
setCertificateEntry("ca", ca)
}
// Create a TrustManager that trusts the CAs inputStream our KeyStore
val tmfAlgorithm: String = TrustManagerFactory.getDefaultAlgorithm()
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
init(keyStore)
}
// Create an SSLContext that uses our TrustManager