Public Key Pinning Extension for HTTP (HTTP 公開鍵ピンニング拡張、HPKP)は、偽造された証明書による中間者攻撃を防ぐため、特定の公開鍵と特定の Web サーバを紐付ける(ピン留めする)ように Web クライアントへ依頼するセキュリティ機能です。
注釈: ここで説明する Public Key Pinning は、Firefox 32 で導入された preload list based key pinning の限定とは異なります。
TLS セッションで用いられるサーバの公開鍵の真正性を担保するため、通常その公開鍵は認証局(CA)の証明書でラップされます。ブラウザなどの web クライアントがこれらの認証局を信頼することで、 認証局は任意のドメイン名に対する証明書を作成できます。もし攻撃者がたった1つの認証局だけでも危殆化させることが出来れば、様々な TLS コネクションで中間者攻撃を仕掛けることが可能になってしまいます。HPKP を利用すれば、どの公開鍵が特定の web サーバと対応するのかをクライアントが知ることが出来るので、先程のような HTTPS プロトコルへの脅威を回避できるようになります。
HPKP は Trust on First Use (TOFU) 技術の1つです。HPKP の HTTP ヘッダが web サーバからクライアントへ最初に送信されて以降、その web サーバに紐付く公開鍵はクライアントで一定期間記憶されます。クライアントが再びそのサーバを訪れた際は、既に HPKP で記憶したフィンガープリントと一致する公開鍵が、証明書チェインにおいて最低 1 つの証明書に含まれていることを確認します。そのサーバから送信されてきた公開鍵が不明なものだった場合、クライアントはユーザに警告を表示します。
Firefox と Chrome では、ピン留めされたホストの証明書チェーンをたどった結果、その終端が(ビルトインの証明書ではなく)ユーザが定義した証明書だった場合、 ピン留めによる認証を無効化します。つまり、独自のルート証明書をインポートしたユーザに対しては、ピン留めによる警告が表示されません。
HPKP を有効にする
自分の管理するサイトでこの機能を有効にするには、HTTPS でアクセスされた時に Public-Key-Pins
HTTP ヘッダを返すだけで簡単に実現できます。以下に例を示します。
Public-Key-Pins: pin-sha256="base64=="; max-age=expireTime [; includeSubDomains][; report-uri="reportURI"]
pin-sha256
- 二重引用符で囲まれた文字列で、Base64 エンコードされた Subject Public Key Information (SPKI) のフィンガープリントです。異なる公開鍵に対する複数のピンを指定することが出来ます。 将来のブラウザでは SHA-256 以外のハッシュアルゴリズムが許容されるかもしれません。証明書や鍵ファイルからこの情報を抽出する方法は次の項で説明します。
max-age
- このサイトへのアクセス時に必要となる(唯一ピン留めされた)鍵について、この鍵をブラウザが記憶するべき時間を指定します。この値は秒単位で表現します。
includeSubDomains
Optional- このパラメータは省略可能です。サイトにおけるすべてのサブドメインにもこのルールが適用されます。
report-uri
Optional- このパラメータは省略可能です。ピンの検証に失敗した際に、失敗した旨を報告する URL を指定します。
補足: 現在の仕様では、本番系で運用されていないバックアップ用の第2のピンを指定することが必須になっています。これにより、既にピンを持っているクライアントからのアクセス性を損なうことなく、サーバの公開鍵を変更することが可能になります。例えば、本番系の鍵が危殆化したときなどに重要となります。
補足: HTTP Pulic Key Pinning の違反レポート機能について、まだFirefox では実装されていません。Chrome ではバージョン 46 からレポート機能がサポートされています。
- Firefox の実装状況について
- Chrome の実装状況について
Base64 エンコードされた公開鍵情報を抽出するには
補足: 以下の例ではサーバ証明書をピン留めする方法を説明していますが、証明書の更新やローテーションを容易にするため、サーバ証明書を発行した CA の中間証明書もピン留めすることを推奨します。
補足: 以下のコマンドは Linux 系 OS でのみ動作します。
まずは証明書や鍵ファイルから公開鍵情報を抽出し、それを Base64 でエンコードする必要があります。
次に示す便利なコマンドで、鍵ファイルや証明書署名要求(CSR)、または証明書から Base64 エンコードされた情報を抽出できます。
openssl rsa -in my-key-file.key -outform der -pubout | openssl dgst -sha256 -binary |
openssl enc -base64
openssl req -in my-signing-request.csr -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary |
openssl enc -base64
openssl x509 -in my-certificate.crt -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary |
openssl enc -base64
以下のコマンドを用いると、Web サイト向けに情報を抽出することができます。
openssl s_client
-servername www.example.com-connect www.example.com:443 | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
HPKP ヘッダの例
Public-Key-Pins: pin-sha256="cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs="; pin-sha256="M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="; max-age=5184000; includeSubDomains; report-uri="https://www.example.net/hpkp-report"
- pin-sha256="cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs="
- 運用されているサーバの公開鍵をピン留めします。
- pin-sha256="M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="
- バックアップ用の鍵をピン留めします。
- max-age=5184000
- この情報を2か月間記憶するようにクライアントに求めています。この2か月という期間は IETF RFC による妥当な値です。
- includeSubDomains
- この鍵ピンニングはすべてのサブドメインで有効となります。
- report-uri="https://www.example.net/hpkp-report"
- ピンの確認に失敗した際の報告先 URL が示されています。
Report-Only header
Public-Key-Pins
ヘッダを用いる代わりに、Public-Key-Pins-Report-Only
ヘッダを利用することも可能です。このヘッダを用いた場合、ピン留めの認証に失敗した場合でも指定した report-uri にレポートが送信されるのみで、 ブラウザが web サーバへ接続することは可能となります。
Public-Key-Pins-Report-Only: pin-sha256="cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs="; pin-sha256="M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="; max-age=5184000; includeSubDomains; report-uri="https://www.example.net/hpkp-report"
HPKP ヘッダを Web サーバから送信できるように設定する
HPKP ヘッダを送信するのに必要な具体的な手順は Web サーバによって異なります。
補足: 以下の例では、2か月間の max-age と includeSubDomains を指定しています。自身のサーバに合った適切な設定をしてください。
HPKP の設定を間違うと、ユーザが長期間接続できなくなってしまう可能性があります!バックアップの証明書を用意したり、CA の証明書をピン留めすることを推奨します。
Apache
次のような行を web サーバの config に追加すると Apache で HPKP が有効になります。mod_headers
モジュールがインストールされている必要があります。
Header always set Public-Key-Pins "pin-sha256=\"base64+primary==\"; pin-sha256=\"base64+backup==\"; max-age=5184000; includeSubDomains"
Nginx
次のような行を追加し、適切な pin-sha256="..." の値を設定すると
nginx で HPKP が有効になります。ngx_http_headers_module がインストールされている必要があります。
add_header Public-Key-Pins 'pin-sha256="base64+primary=="; pin-sha256="base64+backup=="; max-age=5184000; includeSubDomains' always;
Lighttpd
鍵に関する次のような情報(pin-sha256="..." フィールドなど)を含む行を追加すると、lighttpd で HPKP が有効になります。
setenv.add-response-header = ( "Public-Key-Pins" => "pin-sha256=\"base64+primary==\"; pin-sha256=\"base64+backup==\"; max-age=5184000; includeSubDomains")
補足: 以下のように server.module で mod_setenv
をあらかじめ読み込んでおく必要があります。
server.modules += ( "mod_setenv" )
IIS
IIS から Public-Key-Pins
ヘッダを送信するには、以下のような数行を Web.config ファイルに追加してください。
<system.webServer>
...
<httpProtocol>
<customHeaders>
<add name="Public-Key-Pins" value="pin-sha256="base64+primary=="; pin-sha256="base64+backup=="; max-age=5184000; includeSubDomains" />
</customHeaders>
</httpProtocol>
...
</system.webServer>
ブラウザ実装状況
機能 | Chrome for Android | Firefox Mobile (Gecko) | IE Mobile | Opera Mobile | Safari Mobile |
---|---|---|---|---|---|
標準サポート | ? | 35.0 (35) | ? | ? |
? |
[1] Chrome 38 では手動でフラグを有効にする必要があります