Secondary Certificate Authentication in HTTP/2 という仕様について

目次

Secondary Certificate Authentication in HTTP/2

HTTP/2コネクションを確立したあとに、サーバまたはクライアントから追加の証明書を要求・提供できるようにする「Secondary Certificate Authentication in HTTP/2」という仕様が議論されています。(secondary-certsとも呼ばれています)

仕様自体はすでにwg draftとなっています。

数年前にこのブログでも紹介しましたが、デザイン変更もあるため再度簡単に読み直します。

用途

この仕様では、コネクションを確立したあとに、追加の証明書を要求提供できます。サーバ側から要求する場合と、クライアントから要求する場合の両方がありえます。まず、その用途について確認します。

サーバ側から証明書を要求するパターン

Webページでは、提供するページによってセキュリティレベルが異なる場合があります。管理者や、管理者の中でも上位者のみ閲覧できるページもあります。そういったページのみクライアント証明書を要求するといったパターンもあります。

一度コネクションが確立したあとに、追加でクライアント証明書が必要だという場合は、以前までであればTLS renegotiationを行ってクライアント証明書を要求していました。

しかし、HTTP/2ではrenegotiationを禁止しているため、確立したコネクション上で追加のクライアント証明書を要求することができませんでした。

クライアント側から証明書を要求するパターン

HTTP/2では一つのTCPコネクション上で、条件があえば異なるドメインへのHTTPリクエストを送信することが出来ます。例えば、ハンドシェイク中に*.example.comの証明書が得られた場合、かつサーバのIPアドレスが同じであればa.example.comとb.example.comを同じコネクション上でリクエストすることが出来ます。

一方で、「RFC8336 The ORIGIN HTTP/2 Frame」(詳細は過去記事参照)を用いることで、サーバ側から提供できるオリジンリストをクライアントに提供できます。ここでクライアントは、このオリジンのリストにあるサーバ証明書を追加で要求することが出来ます。

こうすることでより多くのドメイン名に対するリクエストで既存のコネクションを利用できるようになります。

また、一度確立したTLSコネクション上で追加の証明書を要求するので、SNIなどの情報によってHTTPリクエスト先が第三者にバレることもありません。

通信フロー

この「Secondary Certificate Authentication in HTTP/2」では4つの拡張フレームを定義しています。

  • CERTIFICATE_REQUEST: 追加の証明書を要求する (TLS1.3のCertificateRequestメッセージを含む)
  • CERTIFICATE_NEEDED: 追加の証明書が必要となっているストリームIDを示す
  • CERTIFICATE: 追加の証明書
  • USE_CERTIFICATE: 追加の証明書を使用するストリームIDを示す

CERTIFICATE_REQUESTフレーム及びCERTIFICATE_NEEDEDフレームの中身については、追加の証明書の要求及び提供する仕組みを定義する「Exported Authenticators in TLS」 (こちらはTLSの話)で定義されているメッセージを利用している

サーバ側から証明書を要求する場合

f:id:ASnoKaze:20190311005237p:plain

クライアントがストリームID N でクライアント証明書が必要な/protectへのリクエストを送信します。そこで、サーバ側からCERTIFICATE_NEEDEDフレームが送られ、クライアントは追加の証明書を提供し、無事そのストリームでHTTPレスポンスをつけとります。

クライアント側から証明書を要求する場合

サーバがORIGINフレームを用いて、提供可能なオリジンのリストをクライアントに通知します。クライアントはその中で必要な証明書を追加で要求します。追加の証明書が得られたら、そのオリジンのドメインに対してHTTPリクエストを送ります。

f:id:ASnoKaze:20190311005418p:plain

DigiCertによるプライベートアドレスの逆引き名の証明書誤発行

DigiCertの発行した証明書で、プライベートアドレス(192.168.1.)の逆引き名である「1.1.168.192.in-addr.arpa」を含む証明書が発行されていることが判明し話題となった。

すでに、revokeされているが、下記の通りSANsに「1.1.168.192.in-addr.arpa」が含まれていた。

  X509v3 Subject Alternative Name: 
      DNS:cynthia.re
      DNS:www.cynthia.re
      DNS:1.1.168.192.in-addr.arpa
      DNS:69.168.110.79.in-addr.arpa

https://crt.sh/?id=1231411316

最初の報告は、2月27日にmozilla.dev.security.policyメーリングリストに投稿された「Possible DigiCert in-addr.arpa Mis-issuance」である。報告者はFQDNが誤って検証されるかテストし、実際に発行されたと言っている。

直後に、DigiCertのJeremy Rowley氏によって調査を開始した旨が報告され、翌28日には誤発行された流れが報告されている。

また、すでに同様の流れで誤発行された証明書はないことが確認されている。

誤発行の流れ

Jeremy Rowley氏によって、調査中ではあるが誤発行の流れが報告されている

WHOIS情報を自動でパースできない場合は、検証エージェントによって手動でWHOIS情報をシステムにアップロードする。この際に、承認範囲をid-addr.arpaと設定したため、1.1.168.192.in-addr.arpaが発行可能となってしまっていた。とのことである。

また、機械的なチェックでもin-addr.arpaがすり抜けていた模様。

in-addr.arpaな証明書

そもそも、グローバル/プライベートを問わず逆引き名、*.in-addr.arpaに対する証明書発行は正しいのか、ユースケースがあるのか個人的には疑問である。

私自身詳しいわけではないが、上記スレッドでも明確な発行基準が存在してないような事が書かれている。

一方で、すでに*.in-addr.arpaに対する証明書は発行されており、CTを見るとすでにパブリックIPに対する逆引き名の証明書は発行されていることが確認できる

f:id:ASnoKaze:20190306005920p:plain
(Google Transparency Report)

その他に、www.175.232.77.in-addr.arpa (アクセス非推奨) といった実際にHTTPSがサービスとして動いてるドメインも存在する。

今後、 .in-addr.arpa に対する証明書はCA/B Forumなど適切な場所で議論にあがるかもしれないが、もし詳しい人がいたら是非教えていただければと思います。

その他のトピック

上記のスレッドで、関連していくつかの話が上がっているのでメモ程度に書いておく

  • WHOISを自動パース出来ない時があることが問題であるため、RDAPなどの利用や連携についての議論
  • IPアドレス証明書については、BGPSecなどで利用されている (https://1.1.1.1 といった例も)
  • PSLの利用

HTTP/3で接続してVPNとして使うMASQUEプロトコルの提案仕様

[2021/05/16 追記] 議論が進み、MASQUEを実現する仕様が幾つか出てます


GoogleのDavid Schinazi氏が「The MASQUE Protocol」という仕様を提出している。

初版のdraftであり、議論の呼び水としての立ち位置が強いがまずは読む。

MASQUE Protocol

MASQUEはMultiplexed Application Substrate over QUIC Encryptionの略称です。この提案仕様では、例えばHTTP/3のQUICコネクションを確立したあとに、そのQUICコネクションをVPNとして使う例を上げています。もちろん、他のプロトコルも通信可能だと思います。

このMASQUEプロトコルの大きな特徴としては、第三者がMASQUEプロトコルの使用に気づかないところである。

  • クライアント・サーバ間の通信を見てもMASQUEプロトコルを喋ってることがわからない
  • 三者がサーバに通信を試みてもただのWebサーバにしか見えない (MASQUEプロトコルを喋るための鍵がないため)

もちろんHTTP/2 over TCPにフォールバックも可能だがパフォーマンス的には劣ることになる。

また、Datagramフレームの利用などを上げているが、具体的なVPN over QUICの仕組みには言及しておらず、方向性含め議論となると思われる。
(「QUICの信頼性のないデータグラム拡張(MESSAGEフレーム/Datagramフレーム)」)

HTTP/3として通信を開始したあとに、クライアントを認証したあとに、そのストリームはMASQUEプロトコル専用に移行します。
f:id:ASnoKaze:20190302143206p:plain

  • MASQUE通信を行うクライアントとサーバは事前に共通鍵を共有しておくか、サーバはクライアントの秘密鍵に対応する公開鍵をリストしておきます。
  • クライアントは通常のHTTP/3で、サーバに接続します。(ラベルEXPORTER-masqueを用いた、TLS keying material exporterで得られた値を後のノンスとして使用する。)
  • クライアントは/.well-known/masque/initialに対してCONNECTメソッドのリクエストを投げます。この際に認証に使う、Masque-Authenticationを付加します。公開鍵を使う場合と、共通鍵を使う場合で異なります
  Masque-Authentication: PublicKey u="am9obi5kb2U=";a=1.3.101.112;
  s="SW5zZXJ0IHNpZ25hdHVyZSBvZiBub25jZSBoZXJlIHdo
  aWNoIHRha2VzIDUxMiBiaXRzIGZvciBFZDI1NTE5IQ=="

  Masque-Authentication: HMAC u="am9obi5kb2U=";a=2.16.840.1.101.3.4.2.3;
  s="SW5zZXJ0IHNpZ25hdHVyZSBvZiBub25jZSBoZXJlIHdo
  aWNoIHRha2VzIDUxMiBiaXRzIGZvciBFZDI1NTE5IQ=="
  • サーバはMasque-Authenticationを見てクライアントを認証します
    • クライアントを正しく認証できなかった場合は、405 Method Not Allowedを返します。これは予期せぬCONNECTメソッドに対する通常の応答であるため、第三者が試みてもMASQUEプロトコルに対応してるかはわかりません。
    • クライアントを正しく認証できた場合は、101 Switching Protocolsを返してそのストリームをMASQUEプロトコル専用にします。

リバースプロキシのエラーを示す Proxy-Statusヘッダの提案仕様

CDNクラウドのロードバランサを使用するのは一般的です。これらのリバースプロキシは様々な理由により502 Bad Gateway504 Gateway Timeoutを返しますが、トラブルシュートするには情報が少ない場合があります。

また、追加の情報を示す場合においても、各社によって異なっています。

そこで、プロキシのエラー情報を示すProxy-Statusレスポンスヘッダを定義する「The Proxy-Status HTTP Header Field」という提案仕様がFastlyのmnot氏らより出されています。

初版のdraftだが読む。

Proxy-Statusヘッダはまずそのエラータイプが示され、Extra Parameters続きます(ない場合もあります)。

f:id:ASnoKaze:20190221012051p:plain
Proxy-Statusの例を示します。

   HTTP/1.1 504 Gateway Timeout
   Proxy-Status: connection_timeout; proxy=SomeCDN; origin=abc; tries=3

上記は、SomeCDNがオリジンであるabcに対して3回思考した後、connection_timeoutのエラーとなったことを示します。
proxy, origin, tries などがExtra Parametersです。

Proxy Status Types

現在はエラーとして様々なタイプが定義されています。

エラーごとに推奨されるステータスコードも併記されている。また、エラーごとに付与されるExtra Parametersが異なるが本記事では省略する。

  • dns_timeout: 宛先ホスト名の解決にタイムアウトした(504)
  • dns_error: 宛先ホスト名の解決エラーとなった(502)
  • destination_not_found: 適切なバックエンドを決定できなかった(500)
  • destination_unavailable: ネクストホップが利用できないと判断した(503)。ヘルスチェックがダウンしてる場合など。
  • destination_ip_prohibited: 宛先IPへの接続を禁止する設定になっている(502)
  • destination_ip_unroutable: 宛先IPへの経路を見つけることができなかった(502)
  • connection_refused: ネクストホップに拒否された(502)
  • connection_terminated: ネクストホップに切断された(502)
  • connection_timeout: ネクストホップへの接続がタイムアウトした(504)
  • connection_read_timeout: 期待すべきデータを待ったが上限にたっした(504)
  • connection_write_timeout: データを書き込もうとしたが出来なかった。バッファがはけなかった場合など(504)
  • connnection_limit_reached: コネクション数の上限に達した
  • http_response_status: 4xxや5xxのレスポンスを受け取った
  • http_response_incomplete: 受け取ったレスポンスが不完全(502)
  • http_protocol_error: HTTPプロトコルエラー(502)
  • http_response_header_block_size: HTTPレスポンスヘッダブロックが大きすぎる(502)
  • http_response_header_size: HTTPレスポンスヘッダのいずれかが大きすぎる(502)
  • http_response_body_size: HTTPレスポンスボディが大きすぎる
  • http_response_transfer_coding: レスポンスのtransfer-codingデコードエラー(502)
  • http_response_content_coding: レスポンスのcontent-codingデコードエラー(502)
  • http_response_timeout: HTTPレスポンスのタイムアウト(504)
  • tls_handshake_error: ネクストホップとのTLSハンドシェイクエラー(502)
  • tls_untrusted_peer_certificate: ネクストホップとのTLSハンドシェイクにおける信頼できない証明書エラー(502)
  • tls_expired_peer_certificate: ネクストホップとのTLSハンドシェイクにおける証明書有効期限切れ(502)
  • tls_unexpected_peer_certificate: ネクストホップとのTLSハンドシェイクにおける期待されない証明書のエラー(502)
  • tls_unexpected_peer_identity: ネクストホップとのTLSハンドシェイクにおける名前の不一致(502)。Subject Alternative Nameの不一致など
  • tls_missing_proxy_certificate: ネクストホップとのTLSハンドシェイクで証明書を要求したが設定されていなかった(500)
  • tls_rejected_proxy_certificate: TLSハンドシェイク中に得られた証明書を拒否した(500)
  • tls_error: ネクストホップとの通信中のTLSエラー(502)
  • http_request_error: オリジンに代わって、プロキシが400, 403などのステータスコードを返す
  • http_request_denied: HTTPリクエストを拒否し、HTTPリクエストはフォワードしなかった。
  • http_upgrade_failed: プロキシとネクストホップ間でHTTP Upgradeに失敗した(502)
  • proxy_internal_error: オリジンと関係しないプロキシ内のエラー(500)

Fake SNIという提案仕様について

SNIを用いた通信のブロッキング及び、「Encrypted SNI拡張」のブロッキングについてはIETFTLS WGでも話題となりました((TLS) SK filtering on SNI, blocking ESNI)。

Encrypted SNIはSNIを暗号化する一方で、ClientHelloにencrypted_server_name拡張をつけます。そのため、経路上の観測者はEncrypted SNIが使われていることを検知できます。

それを回避するために、ニセのSNIをつける「Fake Server Name Indication」というdraftが提出されています。

初版のdraftであり、これから議論のあるところだとは思うが、とりあえず面白そうなので読んでみる。

Fake SNI

Fake SNIを利用するサービスは事前に、偽のホスト名を公表します。DNSを利用することを想定していますが、他の方法でも問題ありません。

DNSを用いる場合は、TXTレコードで下記のように本来のドメイン名に関連付ける偽のホスト名を定義しておきます。

_fakesni.example.com. 60S IN TXT "myfakerecord.com IP"

f:id:ASnoKaze:20190220124940p:plain

  • まずクライアントは通信を行うドメインの偽のホスト名を取得します。
  • クライアントは取得した偽のSNIを設定してTLSハンドシェイクを開始します
  • サーバは、Fake SNIのSNIを受け取った場合に、本来のホスト名の証明書を返します。なおTLS1.3ではCertificateは暗号化される
  • クライアントは本来のホスト名の証明書が取得でき、正しく検証できた場合に通信を継続します。

こうすることで、経路上の観測者にはFake SNI使ってるかどうか区別はつかなくなります。

感想

偽のホスト名は公開されているため、経路上の観測者自身が名前解決を行い記録しておいたり、DNS通信を収集することで、偽のホスト名と紐づく本来のドメインはわかりうる。頻繁に偽のホスト名を変えることで対応はできそうではあるが

また、偽のホスト名として任意のドメインを使えるというのは、経路上の中間装置などにどのような影響を与えうるか気になった。

Fetch Metadataリクエストヘッダについて (Sec-Fetch-*)

ブラウザがリソースをFetchするさいに、そのFetchに関するメタ情報をリクエストヘッダに付与するというのがFetch Metadataという仕様です。

この情報を用いれば、画像の読み込みのFetchで銀行用のAPIが叩かれるはずがないといった、明らかな不正なリクエストを検知することができるようになる。

毎度おなじみGoogleのMike West氏によって仕様「Fetch Metadata Request Headers」が書かれている。

以前「Sec-Metadataヘッダについて」で紹介したSec-Metadataをより改良したものです。

ヘッダを分けることでヘッダ圧縮の仕組みとも相性が良くなっています。

Example

以下のような、そのフェッチに関する情報を示すSec-Fetch-*ヘッダが付与される。

Sec-Fetch-Dest: image
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: cross
Sec-Fetch-User: ?F


なお、Chrome CanaryでGoogleのサイトにアクセスすると、すでにこのヘッダが付いていることが確認できた。
f:id:ASnoKaze:20190215012147p:plain

意味

Sec-Fetch-Dest

Sec-Fetch-Destは、リクエストの先が何であるかを示します。

現在は、下記のどれかになります:
"audio", "audioworklet", "document", "embed", "empty", "font", "image", "manifest", "object", "paintworklet", "report", "script", "serviceworker", "sharedworker", "style", "track", "video", "worker", "xslt", "nested-document".

Sec-Fetch-Mode

Sec-Fetch-Modeは、リクエストのモードを示します。

現在は、下記のどれかになります:
"cors", "navigate", "no-cors", "same-origin", "websocket"

Sec-Fetch-Site

Sec-Fetch-Siteは、リクエストイニシエータのオリジンと、リクエスト先のオリジンの関係を示します。

現在は、下記のどれかになります:
"cross-site", "same-origin", "same-site"

Sec-Fetch-User

Sec-Fetch-Userは、user activationによって行われたリクエストかどうかを示します。
user activationについては、「HTML StandardのActivation」を参照。

ブーリアンの値を取ります。

Signed Exchange Reporting for distributors について

Webサイトを一つに固めて署名して再配布可能にする、Web Packagingという仕組みがあります。

現在、Web Packagingは以下の3つの仕様からなっています。

AMPなどをより標準化された仕組みで実現するために、現在議論が進められています。

HTTPリクエストとHTTPレスポンスの対(HTTP exchanges)を署名したsxgというファイルを再配布するわけですが、このsxgファイルの検証エラーをユーザエージェントからレポートできるようにする「Signed Exchange Reporting for distributors」という議論がされています。

Signed Exchange Reporting for distributors

概要

publisherがarticle.htmlを署名して作成したarticle.html.sxgを、distributorが再配布します
f:id:ASnoKaze:20190211021908p:plain

  • distributorはpublisherからarticle.html.sxgを取得する
  • ユーザエージェントは、distributorに該当のリソースのリクエストを投げます
  • distributorはarticle.html.sxgを返します
  • ユーザエージェントはarticle.html.sxgの証明書および署名を検証します。
  • 署名の検証に失敗したユーザエージェントは、予め指定されたエンドポイントにその旨レポートをPOSTします。通常のレポート先は、distributorになるでしょう。
レポートのポリシー適応

Network Error Loggingの仕組みを用いて、distributorがレポートの送信先エンドポイントを指定します。

NELについては以前説明したとおりです。
asnokaze.hatenablog.com

このようなレスポンスヘッダで、このポリシーを適応しておきます

Report-To: {"group": "sxg-errors",
            "max_age": 10886400,
            "endpoints": [{ "url": "https://report.distributor.example/" }] }
NEL: {"report_to": "sxg-errors", "max_age": 2592000}

NELの仕様にもプルリクが出ている
https://github.com/w3c/network-error-logging/pull/100

レポートの内容

このようなレポートがPOSTされます。

{
  "type": "signed-exchange",
  "age": 1,
  "url": "https://distributor.example/publisher.example/article.html.sxg",
  "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) ...",
  "body": {
    "phase": "sxg",
    "type": "sxg.signature_verification_error",
    "status_code": 200,
    "referrer": "https://www.example/",
    "method": "GET",
    "sxg": {
      "outer_url": "https://distributor.example/publisher.example/article.html.sxg",
      "inner_url": "https://publisher.example/article.html",
      "cert_url": "https://distributor.example/publisher.example/cert",
    }
  }
}

bodyのtypeとかはその他にも増えるのかな?証明書チェーンの辿れない場合とか、証明書の有効期間が合わない場合とか、いくつかのパターンがありそうな気はする。