QUIC標準化現状確認メモ

2018/2
asnokaze.hatenablog.com


Googleの考案したQUICは現在IETFで標準化が進められており、多くの人々によって議論されております。QUICの中身よりも現在の状況について、自分用に整理する。

Issueが追えてないので残念感ある。

概要

UDP上で信頼性のある暗号化されたHTTP/2通信を行うQUICは、もともとはGoogleが考案・実装・実証をすすめられていた。

IETFの会合で数度のBoF(WG設立前の議論)の開催へて、2016年にQUIC WGが出来QUICの標準化を進めることが決定した。

QUIC WGのゴールはCharterにかかれている通り、大まかに以下のとおりである

  • アプリケーションのために、コネクション及びトランスポートの遅延を最小化すること
  • head-of-lineブロッキングの無い多重化を提供すること
  • エンドポイントの変更のみでデプロイ可能とすること
  • マルチパスとFEC(forward error correction)を拡張機能として有効にする
  • TLS1.3を使用した常時暗号を提供すること

また、マイルストーンとしては主要な4つの仕様は 2018年ごろにほぼほぼ標準としては固まった状態となり、IESGに提出される予定になっている

  • Mar 2018 Core Protocol document to IESG
  • Mar 2018 Loss detection and Congestion Control document to IESG
  • Mar 2018 TLS 1.3 Mapping document to IESG
  • Nov 2018 HTTP/2 mapping document to IESG
  • Nov 2018 QUIC Applicability and Manageability Statement to IESG
  • May 2019 Multipath extension document to IESG

7月に行われるIETF99において、以下にあげる仕様(draft04)で相互接続通信ハッカソンが行われる予定になっている。しかし、フル実装ではなく1RTTハンドシェイクにフォーカスしたものである (Home · quicwg/base-drafts Wiki · GitHub)

仕様・ドキュメント

主要仕様

現在の仕様、主要な4つに関してはWGドラフトとなりすでに改版が進んでいる。直近で6/13にdraft-04となっている

Ops

Opsに関して、すでにAdopetされている個人ドラフト

個人ドラフト

その他個人ドラフト

ドキュメント

その他仕様ではないが、参照されるもの

Google QUIC

その他Google QUIC側の資料は「Chromiumのプロジェクトページ」から辿れる。すべての資料ではないが、YoutubeにおいてFECを有効にした際の評価についての資料などもある。

議論/発表資料

直近の議論と、発表資料についてまとめる

IETF QUIC Working Group Interim Meeting

6/6~8にかけて行われた、IETFの本会合との間に設けられているQUIC WGの中間会議

議事録」の通りとなるが、Implementation Draftの議論、幾つかのIssueについての熱い議論及び、下記の発表があった

過去のIETF/Interimの議事録・発表資料はGithubで公開されている
https://github.com/quicwg/wg-materials

The HTTP Workshop

6/12~13にかけて行われた、標準化の場ではないが、仕様の著者、ブラウザベンダ、ミドルウェア実装者、CDNや大手サービスがHTTPについて議論するWorkshopであり、QUICに関する発表もあった。

Next IETF

次のIETFは7/16~21であり、もちろんQUIC WGのセッションもある。スケジュールはすでに公開されており、QUICのセッションは20, 21である。

余談だが、リモートでの参加は誰でも無料で出来る、深夜帯ではあるが興味ある方は下記記事を参照頂ければと思う
qiita.com

CookieのNoHttp属性の提案仕様

以前、「Cookieの仕様改定版、RFC6265bisの議論」でも書いたとおり、IETFのHTTPBis WGではCookieのセキュリティ向上に向けて改訂作業が行われています。

Cookieの改訂版のAuthorでもあるGoogleのMike West氏が、それとは別に新しくCookieに「NoHttp」属性を追加する新しい「Non-HTTP Cookies」という提案仕様を書いています。

この属性の付いたCookieは、サーバには送信されなくなります。

Non-HTTP Cookies

背景

良くも悪くもCookieはSame-Originを超えて共有できます。analyticsのサービスなどは、document.cookieを通して他のページとユーザの状態を共有するものがあります。しかしこの場合は、document.cookieでアクセス出来れば良いようで、HTTPリクエスト送信時にCookieヘッダに付与する必要ないケースが有るようです。(Mike West氏のツイート)


つまり、無駄にCookieを送信していることになり、通信量的にもセキュリティ的にも余計な事をしていることになります。

そこで、それを抑制するNoHttp属性が出てきます

NoHttp属性

NonHttp属性の付いたクッキーはサーバには送信されないようになります。

例えば以下のように設定します

document.cookie = "name=value; Secure; NonHttp";

HTTPヘッダでも同様に設定できます

Set-Cookie: name=value; Secure; NonHttp

但し、もちろん対応していないクライアントはこのフラグを無視するためHTTPリクエストの際に付与されても良いようにしておく必要はあります。

その他

名称については、NoHttp、DOMOnly、DocumentCookieOnlyなどの変更の可能性があるようです。

また、著者が「Cookieは "HTTP State Management" 機能ですが、そこからHTTPを除くユースケースというのは奇妙だが、インターネットは奇妙な場所である」と書かれているのも、また印象的でした。

署名式 SRI(Subresource Integrity)の議論

W3CのWeb Application Securityワーキンググループで、署名式 SRIの議論が出ていたので簡単に書く

背景

セキュリティ向上の目的で、Webページ上で読み込まれるリソース(JavaScript等)を制限する方法に、CSP(Content Security Policy)やSRI(Subresource Integrity)と言った技術がある。

CSP

CSPを利用することで、読み込むJavaScriptを信頼できるURLのみに制限出来る。

CSPのscript-srcディレクティブで、以下のように制限できる。

script-src https://example.com/script/trusted.js


しかし、CSP Is Dead, Long Live CSP! On the Insecurity of Whitelists and the Future of Content Security Policyで述べられているように、URLベースの制限は非常に細かく記述する必要があり、大規模なサービスでちゃんと運用することは難しい面があるようです。

SRI

SRIを利用することで、リソースが本当に意図したものである場合のみ読むこむように出来ます。

scriptタグのintegirty属性にコンテンツのハッシュ値を付与することで、リソースの正当性が評価されます。これは、CDNなどを利用してる時に、CDNによる悪意ある改ざんから防ぐことが出来ます。

<script src="https://example.com/example-framework.js"
        integrity="sha384-Li9vy3DqF8tnTXuiaAJuML3ky+er10rcgNR/VqsVpcw+ThHmYcwiB1pbOxEbzJr7"
        crossorigin="anonymous"></script>

しかし、SRIはリソースの更新の順番に課題があり、リソース自体の更新とscriptタグ側のintegrity値の更新を行われなければなりません。

署名式 SRI

W3CのWeb Application Securityワーキンググループのメーリングリストで、GoogleのMike Westより「Proposal: Signatures in SRI.」として投げかけられました。

まだ、議論の段階だが概要についても、W3Cのリポジトリで公開されている。

Ed25519公開鍵で署名し、リソースの正当性を担保する。scriptタグ側に公開鍵を、HTTPレスポンスのIntegrityヘッダに署名値が含まれる形である。

javascriptタグ

<script src="https://my.cdn.com/whatever.js" integrity="ed25519-[base64-encoded public key]">

HTTPレスポンス

HTTP/1.1 200 OK
Accept-Ranges: none
Vary: Accept-Encoding
Content-Type: text/javascript; charset=UTF-8
Access-Control-Allow-Origin: *
...
Integrity: ed25519-[base64-encoded result of Ed25519(`console.log("Hello, world!");`)]
 
console.log("Hello, world!");

この方式であればCDNを利用していても、与えられた公開鍵で署名値を確認することで、意図してないリソースを読み込む(実行する)事は防がれる。また、javascriptタグ側の変更は不要である点もメリットとしてあり、これでSRIにあった更新順序の問題は解決される。


実際に需要があるのか?古いバージョンのファイルを提供する攻撃が可能か?鍵管理が困難ではないのか?PKIやX.509を利用したほうが良いのではないか?といった疑問については、上記でも上げた概要に書かれている。

Apache2のmod_proxy_http2を試す

去年ぐらいからApache2.4にも実装されたmod_proxy_http2を今更ながら試す。

mod_proxy_http2はリバースプロキシとして動作する際に、バックエンドのサーバと通信する際にもhttp2を使えるようにするモジュールである。

f:id:ASnoKaze:20170531004524p:plain


今回はxenialを使うが、通常降ってくるapache2だとこの機能は入っていないのでざっとtrunkをビルドする。

ビルド

#ちょっと余分なのもあるが
$ apt-get install libnghttp2-dev libexpat1-dev gcc g++ libpcre3-dev libcunit1-dev libev-dev \
                libjansson-dev libjemalloc-dev cython make binutils autoconf automake autotools-dev \
                libtool pkg-config zlib1g-dev libssl-dev libxml2-dev libevent-dev python3.4-dev libevent-openssl-2.0-5


$ svn checkout http://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x httpd-2.4.x
$ cd ../httpd-2.4.x/
$ svn co http://svn.apache.org/repos/asf/apr/apr/trunk srclib/apr
$ ./buildconf

$ ./configure  --enable-proxy-http2 --enable-http2 --enable-proxy-http
$ make

$ sudo make install

設定

# /usr/local/apache2/conf/httpd.conf
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule http2_module modules/mod_http2.so
LoadModule proxy_http2_module modules/mod_proxy_http2.so

Protocols h2 h2c http/1.1
H2Direct on

ProxyPass "/app" "h2c://localhost"

確認

#起動
$ sudo /usr/local/apache2/bin/httpd

#アクセス
$ curl localhost/app

#ログ
$ tail /usr/local/apache2/logs/access_log
127.0.0.1 - - [30/May/2017:15:51:50 +0000] "GET / HTTP/2.0" 200 45
127.0.0.1 - - [30/May/2017:15:51:50 +0000] "GET /app HTTP/1.1" 200 45

http/1.1でアクセスした際に、自身へのプロキシはh2で行ってることが確認できた。

簡単。

あとは、コネクション使いまわすか後日確認する
=> 子プロセス毎にコネクションを再利用するように見えました。多分、再利用条件はRFCの通り(h2cであれば、IPが一緒のことだが、もともとの:authorityじゃないからちょっとよくわからない)

DNS ANAMEレコードの提案仕様

20181019追記
Address-specific DNS aliases (ANAME)」として、新しい draft-02が出されました


20180912追記
この提案仕様はexpireしてしまいました。
完璧に代替するものではありませんが、いくつかの方法について議論がされているようです。


IETFでのDNS関連の標準化は普段追ってないので恐縮だが、DNSOP(DNS OPerations)WGで気になったドラフトがあったので読んで見る。

Address-specific DNS Name Redirection (ANAME)」という提案仕様であり、先日WGドラフトになったようだ。著者は、ISC, PowerDNS, DNSimple の方々でありDNS実装に関わっている人が書いているようだ。

PowerDNSでは、すでにALIASと言う似たような機能が実装中のようですし、CloudflareやAkamaiとも議論している様子が伺える。

背景とCNAME

DNSにはホストの別名を定義するCNAMEレコードという機能があります。このCNAMEレコードは、同一のホスト名(ドメイン)に対して他のレコードと共存することができません。

このようにAレコードとCNAMEレコードを併記することはできません。

hoge        IN      CNAME   fuga
hoge        IN      A       192.168.0.1

そこで特に問題になるのが、Zone Apex(ドメイン名そのもの)のレコードでしょう。example.comというドメインで考えてみましょう。ドメイン名そのものは設定上 @ で記述します。通常は、SOAレコードや、NSレコード、その他のTXTレコードを記述するケースが多いでしょう。

@   IN SOA ns1 postmaster (
                  ...
    )

    IN NS ns1
    IN NS ns2

もちろん、これらのSOAレコードやNSレコードと、CNAMEは併記できません。そのため、@に対してCNAMEは使えないでしょう。CDNを利用する場合はCNAMEでCDNのレコードを参照することが多いですが、example.com自体にCNAMEは指定できないので、CDNは利用できない事になります。

@   IN CNAME fuga

また、ラウンドロビン的に複数のCDNを出し分ける事もできないでしょう。

ANAMEレコード

ANAMEレコードは、CNAMEレコードのように動作しますが、AやAAAAレコードへの問い合わせのみリダイレクトを実施します。そのため、他のレコードと併記することができます。

これを利用することで、Zone ApexでもCDNの利用が可能になります。

権威サーバは、クライアントがANAMEレコードに対応しているかわからないので、自信でANAMEレコードを解決してAレコードと一緒に応答してもよいようです。

仕様

仕様には、キャッシュやDNSSECなど、さらに詳しく権威サーバ及びリカーシブサーバの振る舞いについて書かれています。


参考 (勉強になりました)

NginxのstreamモジュールがDTLSに対応した(パッチ)

NginxのL4レイヤのProxy機能である、streamモジュールのDTLS対応パッチが公開されていたので試す。
(まだ検証段階で、フィードバック募集中らしい)

簡単に言うと、以下のようににDTLS -> Plain UDPや、その逆にPlain UDP -> DTLSへのProxyが出来る。

DTLS -> Plain UDP

f:id:ASnoKaze:20170524014622p:plain

Plain UDP -> DTLS

f:id:ASnoKaze:20170524022625p:plain

Plain UDP -> DTLS -> Plain UDP

2つ組み合わせれば、中間だけDTLSで暗号化することも出来る
f:id:ASnoKaze:20170524014630p:plain

ビルド

今回は、Ubuntu Trustyでビルドする

sudo apt-get install libpcre3-dev

wget http://nginx.org/download/nginx-1.13.0.tar.gz
wget http://nginx.org/patches/dtls/nginx-1.13.0-dtls-experimental.diff

cd nginx-1.13.0
patch -p1 -i ../nginx-1.13.0-dtls-experimental.diff

./configure --with-stream --with-stream_ssl_module --with-debug
make

sudo make install

いつも通り、鍵も準備

openssl genrsa 2048 > server.key
openssl req -new -key server.key > server.csr
openssl x509 -days 3650 -req -signkey server.key < server.csr > server.crt

DTLS -> Plain UDP

設定
#/usr/local/nginx/conf/nginx.conf
stream {
    # please enable debug log
    error_log logs/error.log debug;

    server {
        # add 'udp' and 'ssl' simultaneously to the listen directive
        listen 127.0.0.1:4443 udp ssl;

        # enable DTLSv1 or DTLSv1.2 or both protocols
        ssl_protocols DTLSv1;

        # set up other SSL options as usually
        ssl_certificate server.crt;
        ssl_certificate_key server.key;

        proxy_pass 127.0.0.1:8080;
    }
}
動作確認

クライアント

$ openssl s_client -dtls1 -connect 127.0.0.1:4443
test

バックエンド

$ nc -ul 8080
test

Plain UDP -> DTLS

設定
stream {
    server {
        listen 127.0.0.1:5555 udp;

        # enable SSL to proxy
        proxy_ssl on;
        # enable DTLSv1 or DTLSv1.2 or both protocols
        proxy_ssl_protocols DTLSv1;

        # set up other proxy SSL options as usually
        proxy_ssl_certificate  server.crt;
        proxy_ssl_certificate_key server.key;

        # the backend is a DTLS server
        proxy_pass 127.0.0.1:4433;
    }

}
動作確認

クライアント

$ echo -en "hello"| nc  -u localhost 5555

バックエンド

$ openssl s_server -cert server.crt -key server.key -dtls1 -accept 4433
ACCEPT
...(略)
hello
その他

省くが、Plain UDP -> DTLS -> Plain UDPの変換も上記の設定の組み合わせで実現できた。
使える機会があるかわからないが、普段使ってるNginxでDTLSが受けられるのは結構楽そうだ

RFC8174「RFC 2119のキーワードにおける大文字と小文字の曖昧性」

RFCやInternet-Draftを読んだことある人であれば、文中にMUSTやSHOULDといった大文字のキーワードが用いられてるのを見たことあるかと思います。

たとえば、このように

An endpoint MUST treat this as a stream error (Section 5.4.2) of type PROTOCOL_ERROR.


これらのキーワードはプロトコルの仕様の曖昧性を無くすために 「RFC 2119 Key words for use in RFCs to Indicate Requirement Levels」で定義されています。

しかし、微妙な曖昧性があるためRFC 8174で更新されました。

RFC8174 Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words

「MUST」「SHOULD」「MAY」といった要請の程度を示すキーワードを定義するRFC 2119ですが、その文章にはこう書かれています。

「These words are often capitalized」
(これらの語句は、よく大文字になっています) IPAの翻訳より

このoftenによる微妙な表現のため、"must"や"should"といった小文字の場合にどう解釈するか微妙な曖昧性があります。

RFC8174では下記2点が明記されるようになりました

  • これらの語句は全て大文字の場合のみ、この文書で定義された特別な意味をもつ
  • 小文字の場合は通常の英語の意味をもち、この文書の影響はありません

また、絶対にこれらの語句を使用すべきというわけではない旨が追記されたほか
別の文書から参照する時の例が更新されています

      The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL
      NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED",
      "MAY", and "OPTIONAL" in this document are to be interpreted as
      described in BCP 14 [RFC2119] [RFC8174] when, and only when, they
      appear in all capitals, as shown here.