在 Apache、NGINX 和 Lighttpd 上啟用 HTTP 公鑰固定擴展(HPKP)
![](https://linuxstory.org/wp-content/uploads/2024/06/182724cfrflf9lw44xxich.png)
HTTP 公鑰固定擴展
用你使用的銀行做個例子,它一直使用 CA 公司 A 為其簽發證書。但是在當前的證書體系下,CA 公司 B、CA 公司 C 和 NSA 的 CA 都能給你的銀行創建證書,而你的瀏覽器會毫無疑慮的接受它們,因為這些公司都是你所信任的根 CA。
如果你的銀行實現了 HPKP 並固定了它們的第一個中級證書(來自 CA 公司 A),那麼瀏覽器將不會接受來自CA 公司 B 和 CA 公司 C 的證書,即便它們也有一個有效的信任鏈。HPKP 也允許你的瀏覽器將這種違例行為報告給該銀行,以便銀行知道被偽造證書攻擊了。
HTTP 公鑰固定擴展是一個從2011年開始開發的針對 HTTP 用戶代理(即瀏覽器)的公鑰固定標準。它由 Google 發起,甚至在 Chrome 中實現的固定機制可以使用一個人工維護的網站公鑰固定列表,這個列表包含了固定的幾個網站的公鑰簽名。(LCTT 譯註:Chrome 和 FireFox 32 及以後版本都支持公鑰固定機制,並使用內置的人工維護的公鑰固定列表數據,這些數據隨著瀏覽器軟體的更新而更新,主要包括幾個大型站點。目前還只有 Chrome 38+ 支持通過 HTTP 響應頭傳遞公鑰固定信息。)
以下是 HPKP 的幾個功能簡述:
- HPKP 是在 HTTP 層面設置的,使用
Public-Key-Pins
(PKP)響應頭。 - 該規則的保留周期通過 max-age 參數設置,單位是秒。
- PKP 響應頭只能用於正確的安全加密通訊裡面。
- 如果出現了多個這樣的響應頭,則只處理第一個。
- 固定機制可以使用
includeSubDomains
參數擴展到子域。 - 當接收到一個新的 PKP 響應頭時,它會覆蓋之前存儲的公鑰固定和元數據。
- 公鑰固定是用哈希演算法生成的,其實是一個「主題公鑰信息(SKPI)」指紋。
本文首先會介紹一些 HPKP 工作的原理,接下來我們會展示給你如何得到需要的指紋並配置到 web 伺服器中。
SPKI 指紋 - 理論
以下摘自 Adam Langley 的帖子,我們哈希的是一個公鑰,而不是證書:
通常來說,對證書進行哈希是一個顯而易見的解決方案,但是其實這是錯的。不能這樣做的原因是 CA 證書可以不斷重新簽發:同一個公鑰、主題名可以對應多個證書,而這些證書有不同的延展或失效時間。瀏覽器從下至上地在證書池中構建證書鏈時,另外一個版本的證書可能就替代匹配了你原本所期望的證書。
舉個例子,StartSSL 有兩個根證書:一個是以 SHA1 簽名的,另外是一個是 SHA256。如果你希望固定住 StartSSL 作為你的 CA,那麼你該使用哪個證書呢?你也許可以使用這兩個,但是如果我不告訴你,你怎麼會知道還有一個根證書呢?
相反地,對公鑰進行哈希則不會有這個問題:
瀏覽器假定子證書是固定不動的:它總是證書鏈的起點。子證書所攜帶的簽名一定是一個有效的簽名,它來自其父證書給這個證書專門簽發的。這就是說,父證書的公鑰相對於子證書來說是固定的。所以可推論公鑰鏈是固定的。
唯一的問題是你不能固定到一個交叉認證的根證書上。舉個例子,GoDaddy 的根證書是 Valicert 簽名的,這是為了讓那些不能識別 GoDaddy 根證書的老客戶可以信任其證書。然而,你不能固定到 Valicert 上,因為新的客戶在證書鏈上發現了 GoDaddy 證書就會停止上溯(LCTT 譯註:所以就找不到固定信息了)。
此外,我們是對 SubjectPublicKeyInfo(SPKI)進行哈希而不是對公鑰位串。SPKI 包括了公鑰類型、公鑰自身及其相關參數。這很重要,因為如果對公鑰進行哈希就有可能導致發生曲解攻擊。對於一個 Diffie-Hellman 公鑰而言:如果僅對公鑰進行哈希,而不是對完整的 SPKI,那麼攻擊者可以使用同樣的公鑰而讓客戶端將其解釋為其它組。同樣地,這樣也有可能強制將一個 RSA 密鑰當成 DSA 密鑰解釋等等。
固定在哪裡
你應該固定在什麼地方?固定你自己的公鑰並不是一個最好的辦法。你的密鑰也許會改變或撤銷。你也許會使用多個證書,經常輪換證書的話密鑰就改變了。也許由於伺服器被入侵而撤銷證書。
最容易但是不是太安全的方法是固定第一個中級 CA 證書。該證書是簽名在你的網站證書之上的,所以簽發該證書的 CA 的公鑰肯定是在證書鏈上的。
採用這種方法你可以從同一個 CA 更新你的證書而不用擔心固定信息不對。如果該 CA 發行了一個不同的根證書,也許你會遇到一些問題,對此並沒有太好的解決方案。不過你可以通過如下做法來減輕這種問題的影響:
- 從一個不同的 CA 申請一個備用的證書,並固定該備份。
RFC 裡面說你至少需要做兩個固定。一個是當前連接所使用的證書鏈上的,另外一個是備份的。
另外的固定是對備份公鑰的,它可以是來自另外一個給你簽發證書的不同 CA 的 SKPI 指紋。
在這個問題上還有一種更安全的方法,就是事先創建好至少三個獨立的公鑰(使用 OpenSSL,參見此頁 了解 Javascript OpenSSL 命令生成器),並將其中兩個備份到一個安全的地方,離線存儲、不要放到網上。
為這三個證書創建 SPKI 指紋並固定它們,然後僅使用第一個作為當前的證書。當需要時,你可以使用備份密鑰之一。不過你需要讓 CA 給你做簽名來生成證書對,這可能需要幾天,依你的 CA 的工作情況而定。
對於 HPKP 來說這沒有問題,因為我們使用的是公鑰的 SPKI 哈希,而不是證書。失效或不同的 CA 簽名鏈並不影響。
如果你按照上述方法生成並安全存儲了至少三個獨立的密鑰,並固定它們,也可以防止你的 CA 撤銷你的網站證書並簽發一個假證書時出現問題。
SPKI 指紋
可以使用如下的 OpenSSL 命令來生成 SPKI 指紋,它出現在 RFC 草案 中:
openssl x509 -noout -in certificate.pem -pubkey |
openssl asn1parse -noout -inform pem -out public.key;
openssl dgst -sha256 -binary public.key | openssl enc -base64
結果:
klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=
上面輸入的 certificate.pem
文件是本站(https://raymii.org)的證書鏈中第一個證書。(在寫本文時, COMODO RSA Domain Validation Secure Server CA, 序列號 2B:2E:6E:EA:D9:75:36:6C:14:8A:6E:DB:A3:7C:8C:07
)。
你也需要同樣對你的另外兩個備份公鑰生成指紋。
故障
在寫本文時(2015/1),唯一支持 HPKP 的瀏覽器(chrome)有一個嚴重的問題:Chrome 並不能夠區分 HSTS 和 HPKP 響應頭中的 max-age 和 includeSubdomains 參數。也就是說,如果你的 HSTS 和 HPKP 設置了不同的 max-age 和 includeSubdomains 參數,它們會互相搞亂。關於這個故障的更多信息參見:https://code.google.com/p/chromium/issues/detail?id=444511。感謝 Scott Helme(https://scotthelme.co.uk)發現並告訴我這個 Chromium 項目的問題。
Web 伺服器配置
下面你可以看到三個主流 Web 伺服器的配置方法。這只是一個 HTTP 響應頭,絕大多數 Web 伺服器都可以設置它。它只需要設置到 HTTPS 網站上。
下面的例子固定到 COMODO RSA Domain Validation Secure Server CA
及備份的 Comodo PositiveSSL
CA 上,30天失效期,包括所有的子域。
Apache
編輯你的 Apache 配置文件(如 /etc/apache2/sites-enabled/website.conf 或 /etc/apache2/httpd.conf
),並添加下列行到你的 VirtualHost 中:
# 如需要,載入 headers 模塊。
LoadModule headers_module modules/mod_headers.so
Header set Public-Key-Pins "pin-sha256="klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY="; pin-sha256="633lt352PKRXbOwf4xSEa1M517scpD3l5f79xMD9r9Q="; max-age=2592000; includeSubDomains"
Lighttpd
Lighttpd 更簡單一些,將下列行添加到你的 Lighttpd 配置文件(如 /etc/lighttpd/lighttpd.conf
):
server.modules += ( "mod_setenv" )
$HTTP["scheme"] == "https" {
setenv.add-response-header = ( "Public-Key-Pins" => "pin-sha256="klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY="; pin-sha256="633lt352PKRXbOwf4xSEa1M517scpD3l5f79xMD9r9Q="; max-age=2592000; includeSubDomains")
}
NGINX
NGINX 的配置更簡短。添加以下行到你的 HTTPS 配置的 server 塊中:
add_header Public-Key-Pins 'pin-sha256="klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY="; pin-sha256="633lt352PKRXbOwf4xSEa1M517scpD3l5f79xMD9r9Q="; max-age=2592000; includeSubDomains';
報告功能
HPKP 報告功能允許瀏覽器報告任何違例給你。
如果你在響應頭中添加了附加的 report-uri="http://example.org/hpkp-report"
參數,並用該 URI 處理接收到的數據的話,客戶端會在發現違例時發送報告給你。這個報告是以 POST 方式發送到你指定的 report-uri 上,並以類似下面的 JSON 格式:
{
"date-time": "2014-12-26T11:52:10Z",
"hostname": "www.example.org",
"port": 443,
"effective-expiration-date": "2014-12-31T12:59:59",
"include-subdomains": true,
"served-certificate-chain": [
"-----BEGINCERTIFICATE-----nMIIAuyg[...]tqU0CkVDNxn-----ENDCERTIFICATE-----"
],
"validated-certificate-chain": [
"-----BEGINCERTIFICATE-----nEBDCCygAwIBA[...]PX4WecNxn-----ENDCERTIFICATE-----"
],
"known-pins": [
"pin-sha256="dUezRu9zOECb901Md727xWltNsj0e6qzGk"",
"pin-sha256="E9CqVKB9+xZ9INDbd+2eRQozqbQ2yXLYc""
]
}
非強制,只報告
HPKP 也可以設置為非強制的,可以使用 Public-Key-Pins-Report-Only
來只發送違例報告給你。
這樣可以讓你在網站不可訪問或 HPKP 配置不正確時不固定,之後你可以將這個響應頭改為 Public-Key-Pins
來強制固定。
via: https://raymii.org/s/articles/HTTP_Public_Key_Pinning_Extension_HPKP.html
作者:Remy van Elst 譯者:wxy 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive