TLS1.3の0-RTT通信と、HTTP 4NN(未定) ステータスコードの提案仕様

TLS1.3では、0-RTTハンドシェイクの際にearly_dataとしてアプリケーションデータを送信できます。しかし、この0-RTTハンドシェイクで送信するデータには、リプレイ攻撃のリスクがあります。
そのリスクをさけるための機能をHTTPに追加する提案が出ています。

提案仕様は、「Using TLS Early Data in HTTP」で公開されており、著者はMartin Thomson, Mark Nottinghamらである。

この仕様ではHTTPに以下の機能を追加します

背景

現在標準化の大詰めを迎えているTLS1.3の機能の一つに 0-RTT Data というものがります。

これは、一度TLSハンドシェイクを行ったサーバに対して再度ハンドシェイクを行う際は、ClientHelloと共にアプリケーションデータも送信する手順です。通常のフルハンドシェイク手順よりも早くアプリケーションデータを送信し始めることができます。パケットの往復を待たずに送りたいデータを送ることから、0-RTTハンドシェイクとも呼ばれます。

具体的な手順は、TLS1.3の仕様の2.3節で示されています
f:id:ASnoKaze:20170618234412p:plain:w350

この0-RTTハンドシェイクで送信されるアプリケーションデータはもちろん暗号化されていますが、リプレイ攻撃が可能です。0-RTTハンドシェイクのClientHelloのパケットを観測し、再度サーバに送りつけ多重にリクエストを処理させることができます。

そのため、この0-RTTハンドシェイクで送信するアプリケーションデータはべき等なもの、つまり、なんど処理をしても結果が変わらないものに限られるべきです。HTTPで言えばGETメソッドなどがそう言えるでしょう(そうでないものも多いですが)。

4NN (Too Early)と、Early-Dataヘッダ

この 0-RTT Dataのリスクを軽減するために「Using TLS Early Data in HTTP」という提案がでています。この仕様ではHTTPに以下の機能を追加します

4NN (Too Early)

4NN (Too Early)は、サーバがリプレイ攻撃の危険性がある0-RTTハンドシェイクのearly_dataデータを処理したくないことをクライアントに通知するステータスコードです。

このステータスコードを受け取ったクライアント(User-Agentやプロキシ)は自動でリクエストを再試行すべきではありません、全く新しいコネクションとしてサーバに接続する必要があります。

418 VS その他

具体的なステータス番号については、Author間で議論がありました。まだIANAに登録されていない418を使うという提案がありましたが、Hyper Text Coffee Pot Control Protocolで「418 I'm a teapot」として使用されています。

「418 I'm a teapot」は https://www.google.com/teapot 実際にデプロイされておりますが、HTCPCPはジョークRFCであり、HTTPとは別のプロトコルであるためステタース番号はかぶっても良いという意見です。

また、ジョークRFCによって貴重な番号が消費される事についても意見がついているようです。

しかし最終的には関係者と議論して決めるようです。

Early-Dataヘッダ

TLSを終端するサーバとアプリケーションサーバが別れている構成は一般的です。もちろんCDNやクラウドサービスのTLS終端サービスはそのHTTPリクエストがリプレイ攻撃されても問題ないか判断はできません。

そのため、TLS終端したサーバはそれが元々0-RTTハンドシェイクで送られてきたデータなのか区別できるようにする必要があります。

仕様では、HTTPリクエストが0-RTTearly_dataで送信されていることを示すEarly-Dataヘッダが定義されています。0もしくは1であり、1がearly_dataで送信されていることを示します。

TLSを終端しているサーバがEarly-Dataヘッダを付与します。"Early-Data"ヘッダーフィールドは、ユーザーエージェント(つまり最初のリクエストを送った者)が使用するためのものではありません。

f:id:ASnoKaze:20170619000602p:plain

この時、4NNを受け取った場合の終端サーバはクライアントにそれを転送しなければなりません。

またEarly-Dataヘッダは、4NNステータスコードを理解できることも示しています。

QUIC標準化現状確認メモ

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ハンドシェイクにフォーカスしたものである (First Implementation Draft · 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レコードの提案仕様

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が受けられるのは結構楽そうだ