HTTP Public Key Pinning (HPKP)简介和部署教程(Apache)

HTTP Public Key Pinning (HPKP),中文一般译作“HTTP公钥固定”或者“HTTP公钥钉”,它是一种HTTPS网站防止攻击者使用CA错误签发的证书进行中间人攻击的安全机制,例如有时攻击者可以入侵CA然后偷偷签发证书,现在也有一些还在被浏览器信任的CA频频被曝出签发伪造证书。启用HPKP的网站,由HTTPS网站服务器提供一个公钥哈希列表并使客户端在后续的链接中只接受在列表上的一个或多个公钥。

服务器通过Public-Key-Pins HTTP头向浏览器指定公钥,退一步,也可以通过Public-Key-Pins-Report-Only HTTP头向浏览器报告非法公钥,而非直接拒绝访问。当然,最追求安全的做法就是强制固定公钥。

如今配置了HPKP的网站还不到1%,其原因在于,HPKP的部署是需要深思熟虑的,不恰当的配置会引来诸多麻烦,错误的配置会导致网站长期无法访问,这可以直接毁掉一家大型的公司。

关于HPKP更多的介绍建议直接搜索专业的文档,这里接下来就以Apache服务器端为例讲讲如何在一个网站上使用HPKP。

首先,最关键的一点,确定需要固定的公钥,HPKP一般可以固定三种公钥,一是网站当前的证书公钥,二是包含CA和中级证书的公钥,三是备份公钥,而备份公钥可以是已经签发的网站有效证书公钥,也可以是CA和中级证书的公钥,这里就要考虑哪种部署方案适合该网站。直接固定网站当前的证书公钥无疑是最安全的,但如果哪天更换该证书,而HPKP规则过期时间还没有到,这时候如果没有固定其他有效的公钥,会直接导致网站无法访问。如果固定的是CA和中级证书的公钥,万一这家CA被攻破或者恶意签发了网站证书,你的网站将面临中间人攻击风险,但好处就是,你只要不换CA,且CA的公钥还在继续签发证书,这个固定就是有效的,无论你换了多少该CA的证书。所以,绑定一个备用公钥是很有必要的。

本站目前的选择是,首先固定了Let’s Encrypt Authority X3的公钥,这是本站目前的CA所使用的公钥,是非常安全可靠的。然后固定了另一个大家所熟知但不太靠谱的免费CA为本站签发的有效证书公钥作为备份公钥,因为我不太信任那家CA,所以并没有选择直接固定那家CA的公钥。

选择好了要固定的公钥,接下来一个困难的选择就是HPKP规则过期时间,因为一旦固定的公钥不能用,而HPKP规则过期时间还没到,那网站就无法访问了。时间设置越久,越能有效保护访问的用户,但证书不能用的风险就越大,而时间设置越短,越容易在HPKP规则过期后用户第一次访问时遭遇中间人攻击。本站并不是特别重要的站点,也没有被中间人攻击的价值,所以目前仅仅设置了一周的时间,一些注重安全又走在技术前沿的著名站点往往会设置一个月以上的时间。

接下来,其实设置的过程非常简单。首先,为需要绑定的证书公钥生成SPKI指纹。

openssl x509 -noout -in certificate.pem -pubkey | \
openssl asn1parse -noout -inform pem -out public.key;
openssl dgst -sha256 -binary public.key | openssl enc -base64

输出结果。

YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=

其他要绑定哪些密钥也用同样的方法生成SPKI指纹,然后打开Apache的HTTPS站点配置文件,加入HPKP头。

Header always set Public-Key-Pins "pin-sha256=\"YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=\"; max-age=604800; includeSubDomains"

其中,pin-sha256可以设置多个,max-age为规则过期时间,includeSubDomains是指包含子域名。

然后重启Apache,规则就生效了,很简单,最关键的,还是选择合适的固定公钥以及合适的过期时间。

Apache开启HSTS (HTTP Strict Transport Security)

重定向HTTP至HTTPS并不能解决劫持问题,而且现在很多运营商劫持行为很普遍、WIFI热点劫持也非常多,在HTTP跳转至HTTPS之前就会劫持用户,这些都存在着很大的安全风险。因此,HSTS (HTTP Strict Transport Security,即HTTP严格传输安全) 就显得很重要了。

设置很简单,这里以Apache默认的SSL站点为例。首先启用Mod_Headers模块。

a2enmod headers

然后打开HTTPS站点的配置文件。

vi /etc/apache2/sites-available/default-ssl.conf

在<VirtualHost *:443>中添加如下内容。

# HSTS (mod_headers is required) (15768000 seconds = 6 months)
Header always set Strict-Transport-Security "max-age=15768000; includeSubDomains; preload"

max-age为HSTS过期时间,一般建议不小于18周(10886400秒),这里设置为6个月,includeSubDomains是指包含子域名,为了最大程度地保护该域名下地所有站点,建议加上,而如果某个子域名不强制使用HTTPS,就不要在根域名加。preload代表启用HSTS Preload (HSTS预加载),只有被浏览器预加载的HSTS,才能够保证在任何情况下都不被劫持,否则在第一次访问该站点时还是有被劫持的可能。

最后再重启一下Apache使配置生效。

service apache2 restart

好了,HSTS已经可以使用了。再也不用担心用户访问自己的网站时被劫持了。当然,如果不启用HSTS Preload,第一次访问或者上面设置的半年时间失效后的第一次访问,还是有可能被劫持的。如果要启用HSTS Preload,在设置好上面的内容之后,还要去下面这个网址提交你的域名。方法很简单,照着提示做就好。

https://hstspreload.appspot.com/

需要注意的是,按照提交的要求,preload必须和includeSubDomains一起使用,并且保证max-age不小于18周,当然,使用有效并且可信的SSL证书,设置HTTP重定向到HTTPS这类常规要求肯定是要的。

有人觉得HSTS配置也可以写在.htaccess中,但其实Apache和HSTS规范中都是不建议这样做的,因为这会导致访问HTTP站点时也会收到HSTS Header。当然,这其实不是个问题,因为HSTS本身就是为强制使用HTTPS访问设计的,但规范中是不建议的。