DTLSにコネクションIDを導入する提案仕様

追記 20180702
The DTLS Protocol Version 1.3」で、DTLS1.3の仕様でconnection_id拡張について言及されています


DTLSにコネクションIDを導入する「The Datagram Transport Layer Security (DTLS) Connection Identifier」という提案が出ています。

DTLS1.3の議論を進める中で出てきたトピックであり、既にWG Draft になっています。

DTLSとセッション

DTLSはUDPのようなコネクションのないトランスポート上で暗号化された通信を行うためのプロトコルです。下位層にコネクションのないトランスポートを使っている一方で、現在のDTLSは送信元IP・送信先IP・送信元ポート番号・送信先ポート番号でDTLSのセッションを識別しています。

そのため、それらのうちどれかが変わってしまうと、鍵交換を含むハンドシェイクを初めからやり直す必要があります。特にIoTデバイスなどが普及していく中で、移動する端末や通信頻度が低いもの(NATリバインディングが起こる)は容易に起こるでしょう。さらにそういった端末はリソースが潤沢でない可能性があります。

DTLS Connection Identifier では、DTLSにコネクションIDを導入しIPやポート番号が変わっても通信を維持できるようにします。これにより、ハンドシェイクを1からやり直す手間が省けます。

DTLS Connection Identifier

The Datagram Transport Layer Security (DTLS) Connection Identifier」では、DTLS1.2とDTLS1.3両方について対応します。

この提案仕様では、DTLSに以下を追加します

  • connection_id拡張:ClientHelloとServerHelloで使われる、コネクションIDの使用をネゴシエーションするための拡張
  • RequestConnectionId:DTLS1.3で相手にコネクションIDを要求するメッセージ
  • NewConnectionId:DTLS1.3で相手にコネクションIDを通知するメッセージ (コネクションIDをすぐに使うか、予備か指定可)
  • Record Layer Extensions:レコードに現在のコネクションIDを指定できるように変更される。

例えば、DTLS1.2では、以下のようにcidが追加されます。

     struct {
        ContentType type;
        ProtocolVersion version;
        uint16 epoch;
        uint48 sequence_number;
        opaque cid[cid_length];               // New field
        uint16 length;
        select (CipherSpec.cipher_type) {
           case block:  GenericBlockCipher;
           case aead:   GenericAEADCipher;
        } fragment;
     } DTLSCiphertext;

コネクションIDの使用

DTLS1.3及びDTLS1.2で微妙に手順は異なるが、ハンドシェイク中にconnection_id拡張でコネクションIDの使用が合意されると、以降コネクションIDが含まれたメッセージを交換するようになる。

以下は、DTLS1.2の例である
f:id:ASnoKaze:20171230233015p:plain

将来のQUICをデプロイしやすくするための取り組みと議論 (QUIC GREASE)

MozillaのM. Thomson氏より「More Apparent Randomization for QUIC」というinternet draftが出ています。


## QUICのここまで
QUICはIETFで標準化が進められています。当初は2018年3月が一つのマイルストーンになっていましたが、スコープとマイルストーンの議論をへて、8ヶ月後ろ倒しの2018年11月がIESGへの提出目標となっています(URL)。


後ろ倒しになりましたが、中間ミーティングや実装を持ち寄っての相互接続テストを積極的に行っていくことで議論はより加速する見込みになっています。


また、あわせてスコープの議論も行われ、QUIC V1ではアプリケーションプロトコルとしてHTTPのみをターゲットとすることになっています。V1を策定している最中ですが、将来的にV2が出来た時にいかにそれをデプロイし易いようにするかという議論が盛り上がっています。

Google QUICでは、バージョンアップに伴い既に問題が起きている事が下記の論文で報告されています。
asnokaze.hatenablog.com


TLS1.3でも、この問題に苦しめられ何度も試行錯誤をやりなおしている。

TLS1.3と疎通性の問題

IETF100でも報告されているとおり(URL)、TLS1.2の接続失敗が2.2%だったのに対して、TLS1.3では3.9%でした。


TLS1.3のようにプロトコルのバージョンをあげた時に、FWといった中間装置が不具合を起こし通信をブロックしていたのです。Googleでは、Canonのプリンタなど幾つかを実際に買い動作確認を行ってたりします(URL)


このように、プロトコルのバージョンを上げると中間装置が誤作動を起こしてデプロイが困難になるということがわかってきています。


TLSでは、未知の拡張で不具合が起きないことを確かめるように、以下のようなとりくみも行われています。
jovi0608.hatenablog.com

QUICでの工夫

QUICでは多くの部分を暗号化するとともに、TLSハンドシェイク相当部分を隠蔽する仕組みが有ります。


通常平文であるTLSハンドシェイクのメッセージですが、connection idとV1固有のsaltから導出されるシークレットで暗号化します。これは、メッセージの機密性を得るためのものではなく、将来プロトコルのバージョンを上げた時に、V2固有のsaltを使うようにすることで、V2に対応していないノードはメッセージを見ることが出来ないようするためです。

    quic_version_1_salt = afc824ec5fc77eca1e9d36f37fb2d46518c36639

    handshake_secret = HKDF-Extract(quic_version_1_salt,
                                    client_connection_id)

    client_handshake_secret =
                       HKDF-Expand-Label(handshake_secret,
                                         "QUIC client handshake secret",
                                         "", Hash.length)
    server_handshake_secret =
                       HKDF-Expand-Label(handshake_secret,
                                         "QUIC server handshake secret",
                                         "", Hash.length)

このように、最新のバージョン固有のパラメータを使って隠蔽することで、対応してないなら見ることが出来ないようにすることで、将来のバージョンでの中間装置からの影響を緩和しようとしています。
( quic_version_1_salt は、この仕組が導入された draft06のコミットハッシュです (URL) 。これを使うことでdraft-06以降にうまれた値ということが分かります。 )


さらに、以前書いたVariantsもパケットのフォーマットを将来的に固定することで影響を緩和しようとするものです。
asnokaze.hatenablog.com

More Apparent Randomization for QUIC

話が長くなりましたが、QUICにまだ隠蔽されていないPacket Types・Packet Numbers・KEY_PHASEというフィールドについても隠蔽できないかについて検討しているのが、「More Apparent Randomization for QUIC」です。


このInternet Draftでは、小さなフィールドをシークレットで暗合するのは適切でないとし、幾つかの隠蔽案を検討しています。


それぞれの方式でフィールド毎にメリット・デメリットがあるが、このInternet Draftでは考察までにとどめている。


これらの方式はinvariantsの領域にも使用できるが、将来的にこの仕組を使い続ける必要が出来てしまうため、バージョン固有の領域などに使うことで将来的にも改善していくことができる。

ライブコンテンツにおけるHTTP Rangeリクエストを改善する提案仕様 (RFC8673)

追記2019/11/28
RFC8673になりました


ライブコンテンツやログデータといった常に大きくなり続けるコンテンツに対するRangeリクエストを改善する提案仕様がIETFのHTTPbisで出ています。

その仕様は「HTTP Random Access and Live Content」であり、すでにWGアイテムとなっておりWorking Group Last Callを迎えようとしてます。

現状のライブコンテンツとRangeリクエス

常にコンテンツが大きくなり続けるファイルに対して、Rangeリクエストでデータを受信し続ける場合を考えます。クライアントはRangeリクエストを繰り返すような流れになります。

クライアントはRangeヘッダで1000バイト目より後ろを要求します。Rangeヘッダでは後ろの明示しないことも出来ます

HEAD /resource HTTP/1.1
Range: bytes=1000-
...


サーバはライブコンテンツのうち現在利用可能な範囲を返しますが、Content-Rangeヘッダでレスポンスデータの範囲を示します。 /* をつけることでこのコンテンツの完全な長さが不明であることを示しますが。今回のレスポンスは1000-2000バイトのコンテンツになります。

HTTP/1.1 206 Partial Content
Content-Range: bytes 1000-2000/*
...


クライアントは増えた分のコンテンツを読み込むためにまたRangeリクエストを送信します。

HEAD /resource HTTP/1.1
Range: bytes=2000-
...

提案仕様

上記の例では、Rangeリクエストを繰り返し最新分のデータを取得します。これは、毎回HTTPリクエストを行うのでオーバーヘッドがあるとともに、最新データの取得が送れる事になります。

そこで、Rangeリクエストでもライブコンテンツを受信し続けられるようにするのが今回の「HTTP Random Access and Live Content」という仕様です。

クライアントは"live"コンテンツにRangeリクエストを送る際、end positionに大きな値を指定します

GET /resource HTTP/1.1
Range: bytes=1230000-999999999999


この仕様に対応するサーバは、このリクエストを"live"コンテンツへのリクエストとして解釈し、Content-RangeにRangeヘッダのend positionと同じ値を指定します。

HTTP/1.1 206 Partial Content
Content-Range: bytes 1230000-999999999999/*

上記のレスポンスを受け取ったクライアントは、end positionに同じ値が設定されているのでサーバがこの仕様に対応しており、liveコンテンツのレスポンスであることがわかります。

実装

IETFで発表されたとおり、下記URLで動作確認できます (発表資料)

通常のリクエストをすると、現在可能な部分までのレスポンスしか取得できません

$ curl  http://ietf100.ecaspia.com:8000/live/nasatv.ts -v -H "Range:bytes=40403203-"   >/dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 72.14.183.246...
* Connected to ietf100.ecaspia.com (72.14.183.246) port 8000 (#0)
> GET /live/nasatv.ts HTTP/1.1
> Host: ietf100.ecaspia.com:8000
> User-Agent: curl/7.47.0
> Accept: */*
> Range:bytes=40403203-
>
< HTTP/1.1 206 Partial Content
< Content-range: 40403203-42617155/*
< Date: Sun, 03 Dec 2017 07:09:53 GMT
< Transfer-encoding: chunked
< Content-type: video/mp2t
< Accept-ranges: bytes
< Cache-control: max-age=3600
<


今回の"live"コンテンツ用のRangeリクエストをすると、継続してデータがダウンロードされます

$ curl  http://ietf100.ecaspia.com:8000/live/nasatv.ts -v -H "Range:bytes=40403203-9999999999999"   >/dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 72.14.183.246...
* Connected to ietf100.ecaspia.com (72.14.183.246) port 8000 (#0)
> GET /live/nasatv.ts HTTP/1.1
> Host: ietf100.ecaspia.com:8000
> User-Agent: curl/7.47.0
> Accept: */*
> Range:bytes=40403203-9999999999999
>
< HTTP/1.1 206 Partial Content
< Content-range: 40403203-9999999999999/*
< Date: Sun, 03 Dec 2017 07:12:36 GMT
< Transfer-encoding: chunked
< Content-type: video/mp2t
< Accept-ranges: bytes
< Cache-control: max-age=3600
<

QUIC Invariantsの議論 (RFC 8999)

2021/09/12 追記: このとき議論されていた仕様は RFC8999として標準化されています


QUICは現在IETFで標準化が進めれています。

QUIC WG出来、本格的に標準化が開始して1年ほど立ったところで、スケジュールとスコープの議論(QUIC - Our schedule and scope)が出てきており、QUIC Version1としてどの機能を標準化する話が行われてる一方で、Invariantsというトピックが出てきています。

QUIC Version 1と今後のバージョンで、パケットのレイアウト・パラメータを不変な値、将来のバージョンで変更されないものとするかという議論です。

TLS1.3の議論で起こっているように、プロトコルのバージョンアップによってファイアウォールなどの中間装置が誤作動し通信が阻害される事があるというのはおおきな課題になってきています。

実際、Google QUICでもパケットのレイアウトを変更した所一部のファイアウォールでおかしな挙動をしたことが、Googleの論文でも触れられています。
asnokaze.hatenablog.com

そこで、QUICでは将来のバージョンをデプロイしやすくするために、変更されないパラメータというのを事前に決めていく流れに有ります。

Version-Independent Properties of QUIC

上記のとおり、QUICのパラメータと意味と場所を固定にする提案は、「Version-Independent Properties of QUIC」として提出されています。

まだ議論の最中ですが、今のところ、各パケットのコネクションID・バージョン番号の位置及び、バージョンネゴシエーションパケットは将来も変更されないとしています。

これによってそのコネクションのバージョンが何なのか経路上からも確認できるので、何かしらの処理があるにせよ非対応のバージョンでおかしな挙動をするファイアウォールは少なくなるものと思われます

ロングヘッダ

ロングヘッダでは、ロングヘッダを示す先頭1bit、Connection ID、Versionの位置が将来的にも固定されます
f:id:ASnoKaze:20171202150839p:plain

ショートヘッダ

ショートヘッダでは、ショートヘッダを示す先頭1bit、Connection IDの位置が将来的にも固定されます
f:id:ASnoKaze:20171202151114p:plain

バージョンネゴシエーションパケット

バージョンネゴシエーションパケットでは、ロングヘッダの固定に加え、バージョンが0であり、続いてサポートバージョンが続く事が将来的にも固定されます
f:id:ASnoKaze:20171202151149p:plain

その他

その他にもこのドキュメントでは、現在のQUICではそうなっているが将来変更される可能性もあるので、幾つかの仮定をすべきでない事も列挙しています。

例えば、ロングヘッダがコネクション確立時のみに使用されるという仮定や、パケット番号の出現位置に関する仮定があげれれています。

QUICのSpin Bitの議論

20181111追記
IETF103でも意見が割れ議論となりましたが、Humの結果以下のようになりました。

Spin Bitの仕様は拡張仕様となり必ずしも実装する必要はなくなりました。QUICのコア仕様ではスピンビット用のビットを確保する(好きな台を入れて良い)だけとなっております。なおSpin Bitの領域は、ネゴシエーションもおこなわずに使用されます。


QUICはUDP上でTCP+TLS+HTTP/2の機能を持つ新しいプロトコルであり、IETFで絶賛標準化中です。7月にあったIETF99より、Spin Bitというトピックが議論されているので簡単に書く。

以前、下記記事にも書いたとおりQUICではACKなどを含む殆どの制御情報も暗号化されており、経路上でパケットを観測しても得られる情報は多くありません。
asnokaze.hatenablog.com

パケットロス率やRTTの情報(タイムスタンプ)はネットワークの最適化に利用されているようで、TCPのようにQUICでもそれらの情報を経路上から見れるようなパケットにするか?という議論の中で出てきたのがSpin Bitです。

Spint Bitはパケットが往復するたびにトグルするフラグで、このフィールドは暗号化されていないので経路上からも観測できます。このフラグを観測することで概ねのRTTが推測できる仕組みになっています。

Spint Bit

Spin Bitの仕様については、コアドキュメントではなく「The Addition of a Spin Bit to the QUIC Transport Protocol」という提案にて定義されている。

f:id:ASnoKaze:20171201003908p:plain

ハンドシェイクで使用されるロングヘッダのRTTを測定するのは容易であるので、Spin Bitのフラグはショートヘッダにのみ存在する。Sで表記されている部分がSpin Bitであり、現在仕様とくらべてフラグ部分の位置がずれただけである。

このSpin Bitは以下のように設定されます

  • サーバが送るSpin Bitは、クライアントから受けっ取ったパケットの内、パケット番号が最も大きいメッセージのSping Bitと同じ値になります。
  • クライアントが送るSpin Bitは、サーバから受け取ったパケットの内、パケット番号が最も大きいメッセージのSpin Bitをトグルした値になります。ただし最初に送るときは0。

全てのパケットが順番通りに送られたとすると、Spin Bitは以下のようにトグルされます。パケットがちょうど1往復したときにトグルされることがわかります。

f:id:ASnoKaze:20171201010247p:plain:w300

しかしタイムスタンプなどを含まないため、サーバ・クライアント側での処理時間や何かしらの待ち時間があるとその影響をうける点や、パケットの順番の入れ替わりに対してはヒューリスティックな対応が必要なようです。

その他

このSpin BitをQUICの仕様に組み込むにあたって、デザインチームによる実験が行われています。
これは実際にQUICの実装にSpin Bitを組み込み幾つかの通信状況でRTTの測定を行うことと、プライバシー上の問題の考察です。

詳しい報告は以下のとおりです。
Latency Spinbit Implementation Experience

RTTの測定について一定の有効性があることが確認されるとともに、プライバシーの問題(特に地理情報の特定等)についても現状(あくまで現状)問題がないという話になっています。

今後もこのようにトランスポートレイヤの変更が議論になる可能性はありますが、このようにデザインとして十分に実験・検討する作業が行われるものと思われます。

チェアである、mnotもMLで触れています
Spin bit discussion - where we're at

TLS over HTTPの提案仕様

TLS over HTTPである。HTTP over TLSではない。

Application-Layer TLS」という提案仕様がCiscoの方より提出されている。(仕様中ではATLSと記述される)

HTTP上でTLSレコードを送受信する仕様である。

TLSレイヤの実装変更はなくレコードをHTTPのボディで送受信できるようにする。なお、httpとhttps両方で使用可能である。

また、TLS1.2とTLS1.3などすべてのTLSバージョンに対応する。

来週からはじまるIETF100でも議論されるようなので、かいつまんで読んで見る。

クライアント

クライアントからの基本的なメッセージは、POSTでJSONを送信する。

Content-Typeにはapplication/atls+jsonを指定する

   POST /atls
   Content-Type: application/atls+json

   {
     "session": "<session-string>",
     "records": "<base64 encoded TLS records>"
   }

サーバ

サーバも基本的には同様である。

   200 OK
   Content-Type: application/atls+json

   {
     "session": "<session-string>",
     "records": "<base64 encoded TLS records>"
   }

正しくないリクエストを受け取った際は400 Bad Requestを返す

ハンドシェイク

各アプリケーションは、内部的に持つTLS実装を用いてセッションを開始する。作成されたレコードはJSON形式に変換されHTTP上で送信される。サーバ側は受け取ったJSONよりTLSレコードを復元しTLS実装に渡し、同様にTLSレコードを送り返す。

f:id:ASnoKaze:20171111223136p:plain

WebSocketの利用

HTTPを利用するとサーバからメッセージを送ることは出来ません。TLS closeメッセージなどサーバから送れるように、WebSocketsに通信をアップグレードすることもできます。

なぜこんなことをするのか

最後になったが、何故このようなことをしているのか、ドラフト中にも書かれている。

f:id:ASnoKaze:20171111230217p:plain
ネットワーク管理者がネットワーク接続をするクライアントに信頼できるルート証明書をインストールさせ、ローカルネットワークの出口などでクライアントとサーバの中間に入ってTLSをほどき、平文を確認し再度TLSセッションを貼り直すような環境を想定している。

そのような環境下でもTLS終端装置(中間装置)を安全に通過できるようにHTTP上でTLSレコードのやりとりをするという話のようだ。

追記
関連してATLSの記事を書きまし

Application-Layer TLS の標準化動向
http://asnokaze.hatenablog.com/entry/2018/02/01/084251


HTTPヘッダに構造定義を与える Structured Headers の提案仕様 (draft-00)

2019/12/17追記 draft-14ベースで記事を書き直しました。
(また、最新仕様では 名称が "Structured Field Values for HTTP" に変更されました)
asnokaze.hatenablog.com

以下は古い記事です。


HTTPヘッダには、リストや辞書といった構造を表現するのに決まったやり方はありません。

HTTPヘッダ毎にシンタックスや構造が定義されており、そのためパーサーが再利用出来ません。

mnot氏とphk氏の共著で提出された「Structured Headers for HTTP」仕様では、HTTPヘッダに下記の構造を定義しパーサを再利用できるようします。また、新しいHTTPヘッダを標準化する際も個々別のシンタックス定義を不要にしています。

  • Numbers
  • Strings
  • Labels
  • Parameterised Labels
  • Binary Content
  • Items
  • Dictionaries
  • Lists

この仕様はすでに定義されているヘッダを再定義するものではなく、この定義を使用して定義されるヘッダにて使用される想定です

Structured Headers for HTTP

JSON形式を用いるものや、新しい構造を定義して、HTTPヘッダに構造を与える提案仕様は過去にも有りました(IETF97議事録参照)。しかし、どちらも標準化には至っていません。

上記の議論を踏まえて出てきたのが、「Structured Headers for HTTP」という仕様です。

以前の2つに比べ、既存のHTTPの形式に近しい印象です。今月実施される、IETF100(アジェンダ)でも議論される予定です。


以下では定義されている構造について羅列していきます。仕様ではシンタックスとパース方法も書かれていますが割愛

Numbers

整数。IEEE 754倍精度少数。

ExampleNumberHeader: 4.5

パース及び、精度については議論有り。

Strings

ASCII文字列

ExampleStringHeader: "hello world"
Labels

ラベル(小文字のみ)

ExampleLabelHeader: foo/bar
Parameterised Labels

パラメータ付きラベル(順番に意味はない)

ExampleParamHeader: abc; a=1; b=2; c
Binary Content

バイナリデータ(Base64エンコード)

ExampleBinaryHeader: *cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg
Items

number、string、label、binaryのいずれか

Dictionaries

辞書データ(LabelとItemの組からなる)

ExampleDictHeader: foo=1.23, da="Applepie", en=*w4ZibGV0w6ZydGUK
Lists

Itemのリスト(配列)

ExampleLabelListHeader: foo, bar, baz_45