サービスやリソースの廃止時間を示すSunset HTTP ヘッダ (RFC8594)

Webサービス、古いバージョンのWeb API、期限付きのデータなど、そのURLで提供しているリソースが将来的に廃止するということはよくあることです。

RFC 8594 「The Sunset HTTP Header Field」では、そのようなリソースの廃止時間をHTTPレスポンスヘッダで示せるようになります。

ただしこれはヒントに過ぎず、示した時間までの提供を保証するものではありません。

2016年から、10年間のみ保存されるデータなどには、下記のようなレスポンスヘッダをつけることで、2026年にリソースが取得できなくなることを示すことができます。
(フォーマットは、RFC7231 の通り 「Date/Time Formats」)

Sunset: Wed, 11 Nov 2026 11:11:11 GMT

廃止に関する追加の情報は、Linkヘッダで示すことができます。relation typeとして sunsetを指定します。

Link: <http://example.net/sunset>;rel="sunset";type="text/html"

このポリシーの中では、廃止の範囲(リクエストを受けたURLだけなのか、それとももっと広い範囲なのか)、廃止後の移行先などを書けます。

Cookie の SameSite=Lax をデフォルトにする提案仕様

20191226 追記
SameSite属性のついたCookie自体を拒否する古いクライアントにご注意ください
https://sites.google.com/a/chromium.org/dev/updates/same-site/incompatible-clients

20190823 追記
suidenOTI さまよりご指摘いただきました

不具合があったためChrome80でのリリースに延期になったようです。
https://www.chromestatus.com/feature/5088147346030592
https://www.chromestatus.com/feature/5633521622188032

20190523 追記
Firefoxでも同様の動きがあります
https://groups.google.com/forum/#!msg/mozilla.dev.platform/nx2uP0CzA9k/BNVPWDHsAQAJ


開催中のGoogle I/O で、SameSite属性のないCookieをSameSite=Laxとして扱うようにしていくという話があったようです
blog.chromium.org

SameSite=Laxになると、img, iframeやxhrなど送信される他サイトへのHTTPリクエストにおいてthird party cookieがつかなくなります。これによって、cookieの露出を控える事ができます。(SameSite = None とすることで引き続きトラッキングは可能)

Google調査によると SameSite属性のついたCookieはまだ0.1%以下であり、まだまだ普及できておらず、今回デフォルトでSameSite=Laxとする動きになったのだと思います。

この挙動は、すでにChrome Canaryでchrome://flagsより設定することができます。
f:id:ASnoKaze:20190509004402p:plain

提案仕様

また、上記のアナウンスに続いて、IETF側でもGoogleのMike West氏より「Incrementally Better Cookies」という提案仕様が出ております。

同氏がメーリングリスト投げた「Incremental improvements to cookies.」でも書かれている通り、Cookieを段階的に改善しようとしており、この仕様では

  • 1. デフォルトで、Cookieを「SameSite = Lax」として扱う
  • 2. 開発者が明示的に `SameSite = None`を設定することにより現状の振る舞いのようにできるが、そうするときは` Secure`属性が必要

にするという提案である。

同氏の関連活動

Mike West氏は、Cookieに関わる多くの改善を提案している

SameSite属性の仕様自体は「Same-Site Cookies」で書かれているが、下記の記事の通り、RFC6265bisに統合される流れである
asnokaze.hatenablog.com

また、新しい仕組みも検討中であったが、フィードバックをうけCookieを段階的に改善しようという流れのようだ
asnokaze.hatenablog.com

Cross-Origin-Opener-Policyについて

Cross-Origin-Opener-Policy (COOP)は現在、ChromeFirefoxで実装が進められている機能です。

仕様としては、whatwgで長らく議論がされており、おそらく仕様に入るでしょう

面白そうなので、簡単に読んで見る。今の所下記ドキュメントが定義のようだが、適宜議論を参照のこと

間違ってたらご指摘ください

Cross-Origin-Opener-Policy とは

ユーザがサイトAを閲覧しているとき

サイトA から サイトBをウィンドウとして開いた場合 (noopnerはつけてない)、Bはwindow.openerを介してAにアクセスすることができます(仮にAとBのオリジンが違っていても、制限はありますがAのプロパティにアクセスできます。)
f:id:ASnoKaze:20190508015638p:plain

このようなアクセスは、サイトのアイソレーション上好ましくありません。このようなことを防ぐために、Cross-Origin-Opener-Policyヘッダを利用します。もし、指定されたPolicyに合わない場合は、上記のような繋がりは解除されます(ウィンドウを閉じて開き直したのと同じ状態)

Cross-Origin-Opener-Policyは下記のような値を取ります。

Cross-Origin-Opener-Policy = same-origin
Cross-Origin-Opener-Policy = same-site
Cross-Origin-Opener-Policy = same-origin unsafe-allow-outgoing

A及び、Bそれぞれへのアクセスした際はポリシーに合わず、openerがnullを返すようになります

  • Aもしくは、BのどちらかのみにレスポンスヘッダでCross-Origin-Opener-Policyが設定される
  • AとBのCross-Origin-Opener-Policyのsameness (same-origin or same-site)が異なる
  • 値がsame-originだが、AとBのオリジンが異なる
  • 値がsame-siteだが、AとBのホスト名が異なる

こうすることで、noopenerを指定できる開く側だけでなく、開かれる側からもopenerのつながりを解除することができるようになります。(unsafe-allow-outgoingを指定すると開く側のときだけ許可する)

おまけ

議論の変遷のなかで、openerのポリシーへとヘッダ名とともに変遷しており、議論を追うのが大変だった...

新しいWebの双方向通信 "WebTransport" について

関連記事




WebTransportという新しい双方向通信フレームワークの議論が始まっている。

GoogleのPeter Thatcher氏らによって、W3C WICGにプロポーザルが投げられています。
discourse.wicg.io

WebTransportは、WebSocketのようなAPIをもち、QUICやHTTP/3上で多重化されたストリームを利用し、ヘッドオブラインブロックのない通信を行えるようにするというのがモチベーションのようです。(実際に使用する"トランスポート"はプラガブルな設計になっている)

また、TCPとは異なり、パケロスしても再送を行わないPartial reliabilityなども使えるようになる。そのようなAPIも改めて定められる。

その他にもコネクションのマイグレーションをサポートしているトランスポートであればIPが変わってもコネクションを維持できる。

WEBRTC-QUICで同様のことは可能だが、ICEを必要とせずクライアント・サーバの通信ユースケースに絞っている。

QUICについては以前簡単に書いたとおり
asnokaze.hatenablog.com

仕様

すでにAPIプロトコルの仕様が書かれている。

API

WebTransport」として、WebRTC-QUICのリポジトリ下に、APIに関する仕様が書かれている。

その中で書かれている例の一つをとりあげると、再送を行わないメッセージの送信は以下のようになっている。

let transport = getTransport();
let messages = getMessages();
for (msg in messages) {
  transport.createSendStream({disableRetransmissions: true}).write({data: msg, finished: true});
}
プロトコル

プロトコルの仕様としてすでに下記の3つのドキュメントがIETFに提出されている。

1つ目は概要であり、WebTransportのトランスポートが持つ機能要件について書かれている。

例えば、データ通信としてストリーム方式・データグラム方式を持つ(パケットのデータ境界をアプリケーション側で維持するか)などである。

その他にも追加の機能として、トランスポートのストリームの独立性、Partial reliability、コネクションプール (1つのコネクションを再利用し輻輳制御を行える)、Connection mobility (IPが変わってもコネクションが維持可能)といった項目だしを行っている。

2つ目と3つ目はそれぞれ、WebTransportをHTTP/3、QUIC上で行えるようにする仕様を定義している。特にHTTP/3ではサーバからストリームを開始できるうようにWebTransport用のストリームを定義している他、追加のフレームも定義している。

(詳細は機会があればまた今度)

リンク

vせんせいも、WebTransportついて書かれております
medium.com

QUICの暗号化と鍵の導出について

QUIC, HTTP/3 関連記事


HTTP/3というかQUICの通信がどのように暗号化されているのか勉強がてら軽くまとめる。(執筆時点でQUIC-TLS Draft 19)

QUICでは、通信の多くが暗号化されています。アプリケーションデータはもちろんのこと、ACKや切断といった制御情報も暗号化されます。さらにパケット番号やフラグが含まれるQUICパケットのヘッダ部分も暗号化されています。

これらの通信の保護がどのように行われているか見ていきます。


先にQUICの概要についてざっとここらへんを見ていただけると良いかと思います。
asnokaze.hatenablog.com

目次

QUICのハンドシェイク

まずは、QUICのハンドシェイクを見ていきます。

TLS1.3のメッセージを流用しており、QUICの上でTLS1.3のメッセージをやりとりしています。
f:id:ASnoKaze:20190421222513p:plain
(引用 QUIC Security)

  • コネクションを開始するクライアントは、InitialパケットにCRYPTOフレームを格納し、ClientHelloのメッセージを送信します。(これはInitial Keysで暗号化されます)
  • もし、0-RTTでEarly Dataを送る場合は送信します。(これは、Early Data (0-RTT) Keysで暗号化されます)
  • サーバはClientHelloに対してInitialパケットにCRYPTOフレームを格納し、ServerHelloのメッセージを送信します。(これはInitial Keysで暗号化されます9
  • ServerHelloが送信できれば、Handshake Keysが導出できるので、サーバはHandshakeパケットにCRYPTOフレームを格納し、TLSハンドシェイクの残りを送信します(EncryptedExtensions, Certificate, CertificateVerify, Finished)。(これは、Handshake Keysで暗号化されます)
  • クライアントがHandshakeパケットを受け取ると、同じくFinishを送るためにHandshakeパケットにCRYPTOフレームを格納し送信します。

こうしてアプリケーションデータを暗号化するための、Application Data (1-RTT) Keysが導出できアプリケーションデータが送受信できます。

QUICでは、下記の4つの鍵が登場します。その鍵によって各パケットのペイロードが保護されます。上記図では、枠線の色ごとに使用する鍵が対応しています。

  • Initial Keys
  • Early Data (0-RTT) Keys
  • Handshake Keys
  • Application Data (1-RTT) Keys


TLS_AES_128_CCM_8_SHA256を除いてTLS1.3で定義される暗号スイートが利用できます。この暗号スイートを用いて暗号化及び(ハッシュ関数を用いて)鍵の導出が行われます

ショートパケットとロングパケット

パケットのペイロードの暗号化に先立ち、QUICパケットに関して簡単に復習します。

ハンドシェイク中のQUICパケットはすべてロングパケットです。アプリケーションデータはショートパケットで送信されます。

ハンドシェイク中に登場したようにロングパケットとしては、Initialパケット、Handshakeパケット、0-RTTパケットなどがあります。また、紹介しませんがRetryパケットといったものもあります。それぞれロングパケットヘッダ領域を持ちますが、それ以外はパケットごと固有になります。

例えば、Handshakeパケットは下記のようになります。
f:id:ASnoKaze:20190422101405p:plain

ペイロード領域に、TLSメッセージを含むCRYPTOフレームや、Ack情報を格納するACKフレームなどが含まれます。


アプリケーションデータやコネクションの制御情報を含むショートパケットは以下のようになっています。
f:id:ASnoKaze:20190422101518p:plain

ロングパケットに比べ一部の領域が省略されていることが分かります。

鍵の導出

Initial Keys

ClientHello, SeverHelloを運ぶInitialパケットを保護するInitial Keysは特殊であり、仕様に定義されるinitial_saltと送信先コネクションIDから導出されます。

具体的には下記の通りに、client_initial_secret とserver_initial_secret が導出されます。

   initial_salt = 0xef4fb0abb47470c41befcf8031334fae485e09a0
   initial_secret = HKDF-Extract(initial_salt,
                                 client_dst_connection_id)

   client_initial_secret = HKDF-Expand-Label(initial_secret,
                                             "client in", "",
                                             Hash.length)
   server_initial_secret = HKDF-Expand-Label(initial_secret,
                                             "server in", "",
                                             Hash.length)

saltはdraftバージョンで固有。今後変わる可能性があります。つまり、対応しているdraftバージョンでないと正しく復号できません。実装は、対応してない仕様を処理しようとして不具合を起こすことがありません。

少々古いですが下記に書いたとおりです。
asnokaze.hatenablog.com

そのほかの鍵の導出

それ以外の Early Data (0-RTT) Keys, Handshake Keys, Application Data (1-RTT) Keysは、TLS1.3の手順で導出されたSecretよりさらにそこから導出されます。

QUICのハンドシェイクではTLS1.3のメッセージをやり取りしています。TLS1.3の鍵導出は「RFC8446 7.1. Key Schedule」に書かれているとおりです。

下記はQUICにおける鍵スケジュールです。黄色いのはTLS1.3と同じですが、導出されたSecretからさらにQUICで使うSecretが導出されています。それが青い部分です。(図は、それぞれHKDF-ExtractとHKDF-Expand-Label)

f:id:ASnoKaze:20190421224549p:plain
(引用: QUIC Key Schedule)

QUICでは下記のそれぞれの鍵として

  • Early Data (0-RTT) Keys
  • Handshake Keys
  • Application Data (1-RTT) Keys

下記の秘密地をTLSのSecretより導出します

  • AEAD key (Label: quic key)
  • IV (Label: quic iv)
  • header protection key (Label: quic hp)

Application Data (1-RTT) Keysのみ、導出したSecretはKey Updateによって更にアップデートされていきます。

パケット保護

ペイロードの保護

各QUICパケットのペイロードは下記のように暗号化されます。

f:id:ASnoKaze:20190421225717p:plain
(引用 QUIC Security)

送信するパケットのパケット番号とIVの排他的論理和演算をとってNONCEとします。暗号化するパケットペイロードと、暗号化はしないが改ざんさてないことを確認するAADとしてパケットヘッダを入力します。あわせてNONCEと導出した鍵を入力します。

そうしてProtected Payloadを得ます。

復号する場合は、同様に求めたNONCE、鍵、暗号文を入力し平文を得ます。(実際には後述のパケットヘッダの復号を先に実施する必要があります。)

パケットヘッダの保護 (Header Protection)

QUICではペイロードのみならず、ロングパケット、ショートパケットともにケットヘッダの一部も保護されます。具体的にはパケット番号や鍵フェースなどです。

ヘッダパケットは下記のように、Protected Payloadより導出したMaskで保護されます。
f:id:ASnoKaze:20190421230951p:plain
(引用 QUIC Security)

  • Protected Payloadからサンプリングしたデータを、先に導出したHeader Protecion用の鍵でAES-ECB暗号化する形でMaskを計算します。
  • 得られたMaskを使って、ヘッダのフラグ部分とパケット番号部分の排他的論理和を取ります。

(サンプリングする箇所(サンプリングオフセット)はコネクションID長などから決定されます。)

こうしてヘッダ部分も保護されます。

復号する場合は、同様にProtected PayloadからMaskを取得しヘッダ部分を復元します。こうしてやっと、ペイロード部分の復号が可能になります。

Key Update

Key UpdateはTLS1.3とは異なり、パケットヘッダのKey Phaseビット(1bit)をトグルすることで、鍵のアップデートをピアに通知します。

しかし、この仕様は現在議論中で、新しくKEY READY bitを導入することも検討されています。

github.com

その他

実際にはパケットの順番などが入れ替わるため、再送処理や、それに伴う鍵の保持期間など細かい話がたくさんあります。。。

難しい。

この記事を書くにあたり大変参考になりました

ありがとうございます。

(RFC8740) HTTP/2においてTLS1.3のpost-handshake authenticationの禁止

2020/02/23 追記
下記提案はRFC8740として標準化されました
https://tools.ietf.org/html/rfc8740


GoogleのDavid Benjamin氏より「Using TLS 1.3 with HTTP/2」という提案仕様が出されています。

この仕様自体は、単純にHTTP/2においてTLS1.3のpost-handshake authenticationを禁止するものです。

HTTP/2でTLS1.2を使用している場合はrenegotiatoinが禁止されており、同様の課題をもつTLS1.3のpost-handshake authenticationも明示的に禁止するというのがこの提案です。

背景

Webサービスを利用している際、特定のURL(管理用ページなど)だけセキュリティーレベルが高く、クライアント証明書を用いた認証を行いたい場合があります。

一般的にTLS1.2ではコネクションを確立したあとにクライアント証明書を要求する場合はrenegotiationという手順をとっていました。

しかし、HTTP/2では一つのコネクション上でHTTPリクエストが並列的に送信されます。このTLS1.2のrenegotiationでは、どのHTTPリクエストによってrenegotiationがトリガされたかクライアントは分からないという課題がありました。

(TLS1.2のrenegotiationが脆弱だという話もありつつ。)

TLS1.3では、post-handshake authenticationという形でコネクションを確立したあとにクライアント証明書の要求/送信が行えるようになりました。

しかしHTTP/2を利用しているとき、多重化されたHTTPリクエストのどれがpost-handshake authenticationのトリガになったかという課題は同じです。ですので、改めてHTTP/2でTLS1.3を利用している場合、post-handshake authenticationの禁止を明示します。

Secondary Certificate Authentication in HTTP/2

それでは、追加でクライアント証明書を要求する場合どうするのかというと、別の提案仕様が提出されています。

詳細は以前書いたとおりです。
asnokaze.hatenablog.com

この仕様では、どのストリームが証明書を要求しているかを関連付けることが出来、HTTP/2レイヤで証明書の要求及び送信を行うことで先の課題を解決しています。

もともとはこちらの提案仕様が先に出されていたわけですが、議論の結果、post-handshake authenticationの禁止は別提案として明文化するようになりました。

その話が書かれたメールは下記です
lists.w3.org

「Address-bound Token for QUIC」が面白い

FastlyのKazuhoさんが「Address-bound Token for QUIC」という提案仕様を出している、この中で出てくる "Sharing the Congestion Controller" という仕組みが非常に面白かったので、簡単に書いてみようと思う。

address-bound token

この提案では、同一エンドポイント間で複数のQUICコネクションをはる際に、それらのコネクションが同一エンドポイント間でやりとりされていることを確かにする「address-bound token」というものを定義します。

最初のコネクションでサーバからトークンを払い出し、クライアントが続けて同じホスト名か、IPとポートが同じサーバにコネクションを確立する際にこのトークンを提出します。
f:id:ASnoKaze:20190411014412p:plain
(transport parameterでaddress_bound_tokenaパラメータを設定した場合のみ、NEW_TOKENがaddress-bound tokenになります)

QUICはUDPを使用しますが、もちろん輻輳制御が行われます。この「address-bound token」を使うことで、同一エンドポイント間における複数のQUICコネクションで輻輳コントローラの状態を共有できるようになります(Sharing the Congestion Controller)。

OSではなくユーザランドで動作するため、このようなコネクションと輻輳コントローラを分けて関連付けられるようになる。

Sharing the Congestion Controller

QUICではアンプ攻撃の対策として、そのパケットが確かにIPの所持者から送信されている事が確認できるまで、データの送信量が制限されています。address-bound tokenは払い出し先のIPがわかっているので、そのIPからaddress-bound tokenが提出されれば、確かにIPの所持者であるため、この制限は必要なくなります。(払い出したIPとは別の場合は、path validationを行うべきです。)

Sharing the Congestion Controllerによって、新しいコネクションはスロースタートする必要がありません。

送信ウィンドウサイズを共用しますが、それらをどう分配するかは送信者が決めます。

感想

Sharing the Congestion Controller すごい面白い。CDNのように複数のホスト名をホストしているとより有用そう。

Initial + 0RTTの場合はaddress-bound token使えるのかな。リプレイ(正しいIP)された場合に該当コネクションに不要なデータがより送られそうな気もするが大量にはならないか、ハンドシェイク周りの部分自信がない。