HTTP Public Key Pinning 介绍

提醒:本文最后更新于 2148 天前,文中所描述的信息可能已发生改变,请谨慎使用。

上篇文章中,我介绍了由 Google 推动的 Certificate Transparency 技术,它旨在通过开放的审计和监控系统,提高 HTTPS 网站证书安全性。本文要介绍的 HTTP Public Key Pinning(HPKP),也是用来防范由「伪造或不正当手段获得网站证书」造成的中间人攻击,但有着与 CT 不同的思路。

我们知道,受信任的 CA(证书颁发机构)有好几百个,他们成为整个网站身份认证过程中一个较大的攻击面。现有的证书信任链机制最大的问题是,任何一家受信任的 CA 都可以签发任意网站的站点证书,这些证书在浏览器看来,都是合法的。前面提到的 CT 技术能改善这种情况,但 CT 还没有普及,现阶段浏览器不能贸然阻断没有提供 SCT 信息的 HTTPS 网站。

HPKP 技术给予我们主动选择信任 CA 的权利。它的工作原理是通过响应头或者 <meta> 标签告诉浏览器当前网站的证书指纹,以及过期时间等其它信息。未来一段时间内,浏览器再次访问这个网站必须验证证书链中的证书指纹,如果跟之前指定的值不匹配,即便证书本身是合法的,也必须断开连接。

HPKP 官方文档见 RFC7469,目前 Firefox 35+ 和 Chrome 38+ 已经支持。它的基本格式如下:

Public-Key-Pins: pin-sha256="base64=="; max-age=expireTime [; includeSubdomains][; report-uri="reportURI"]

各字段含义如下:

  • pin-sha256 即证书指纹,允许出现多次(实际上最少应该指定两个);
  • max-ageincludeSubdomains 分别是过期时间和是否包含子域,它们在 HSTS(HTTP Strict Transport Security)中也有,格式和含义一致;
  • report-uri 用来指定验证失败时的上报地址,格式和含义跟 CSP(Content Security Policy)中的同名字段一致;
  • includeSubdomainsreport-uri 两个参数均为可选;

pin-sha256 应该如何指定?显然,为了验证合法性,服务端给出的 pin-sha256 必须由网站当前证书链中的证书生成。例如我的网站证书有三级,用根证书、中间证书、站点证书中的任何一个生成指纹都可以。用站点证书生成指纹的好处是安全性最高,缺点是证书重签之后指纹就变了,如果之前没提供备用指纹,老用户无法访问;用根证书生成指纹安全性最差,因为每个根证书都对应很多中间证书,攻击者只要攻破其中一个,就可以签出能被 HPKP 策略信任的站点证书。

综合考虑安全性和易用性,一般推荐使用中间证书生成指纹;用知名 CA 的根证书也可以;不推荐使用站点证书,除非充分了解后果并且指定了有效的备用指纹。

备用指纹是为那些必须更换中间或者根证书的场景准备的 —— 例如原来的 CA 突然倒闭或者被黑了。本站当前使用的证书由 RapidSSL SHA256 CA - G4 签发,我的 pin-sha256 包含了它的指纹;同时我还用 Let's Encrypt Authority X1 这个中间证书生成了备用指纹。这样如果后续本站改为使用 Let's Encrypt Authority X1 签发的证书,老用户也不会受影响。

使用 openssl 生成证书 Public Key 指纹很简单,下面简单介绍下。

建议先验证证书是否有误:

openssl x509 -in intermediate.pem -noout -subject

subject= /C=US/O=GeoTrust Inc./CN=RapidSSL SHA256 CA - G4

通过 CN(Common Name)字段可以确认这就是本站的中间证书。接着,生成 Public Key:

openssl x509 -in intermediate.pem -noout -pubkey | openssl asn1parse -noout -inform pem -out public.key

生成指纹:

openssl dgst -sha256 -binary public.key | openssl enc -base64

aef6IF2UF6jNEwA2pNmP7kpgT6NFSdt7Tqf5HzaIGWI=

按照同样方法,生成其它中间证书的备用指纹。最后修改 Nginx 配置并重新加载即可。以下是本站的 HPKP 配置:

add_header Public-Key-Pins 'pin-sha256="aef6IF2UF6jNEwA2pNmP7kpgT6NFSdt7Tqf5HzaIGWI="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; max-age=2592000; includeSubDomains';

一切妥当后,可以通过 SSL Labs 这个服务来验证 HPKP 是否正常:

ssllabs of mailseason

也可以在 Chrome 中访问 chrome://net-internals/#hsts 页面,查看指定域名的 HPKP 信息:

public key pins of mailseason

也许有人要问,如果用户第一次访问就被劫持怎么办?

跟 HSTS 一样,HPKP 依赖于服务端响应头,对于用户首次访问就被劫持这种情况无能为力。这个问题也只能通过浏览器内置 Preload List 来解决,现阶段 Chrome 已经内置了自家域名和各大知名网站的 HPKP 信息。不同于 HSTS Preload List,目前我没有找到个人网站申请加入 HPKP Preload List 的入口,如果你知道怎么做,不妨留言告诉我:)

更新:Chrome 69 将移除对 HPKP 的支持,详情请点击查看

本文链接:https://mailseason.com/post/http-public-key-pinning.html参与评论 »

--EOF--

提醒:本文最后更新于 2148 天前,文中所描述的信息可能已发生改变,请谨慎使用。

专题「HTTP 相关」的其他文章 »

Comments