DNSサーバがDoHに対応しているか確認できるようにする提案仕様

今使っているDNSサーバがDNS over HTTPS (RFC8484)に対応しているか確認する方法は標準化されていません。

例えばChromeでは、どのDNSサーバがDoHをサポートしているかソースコード(URL)にハードコードされています。

通常、DHCPや、IPv6 Router AdvertisementsでDNSサーバを指定する場合はIPアドレスで指定されます。それが、DoHやDoTに対応しているか識別出来るようにするというのが今回の話です(ちなみに、HTTPSで繋いでみるという方法はうまくいきません)

この問題を解決するために「Discovery of Designated Resolvers」という提案仕様が出ています。すでにADD WGにAdoptionされております。

今回はこの仕様を簡単に読んでいこうかと思います。

Discovery of Designated Resolvers

Discovery of Designated Resolvers」は、Apple, Cloudflare, Fastly, Microsoftの方による共著になっています。

具体的な手順については、大きく分けて2つに別れており、対象のDNSサーバのホスト名がわかってる場合と、分かってない場合があります。分けて説明していきます。

ホスト名が分かっている場合

resolver.example.com がDoHに対応しているか確認する場合、_dns.resolver.example.com のSVCBレコードを確認します。

SVCBレコードとは現在標準化が進められているリソースレコードであり、そのドメインで提供しているサービス及び接続方法(alpnやESNI情報)が記述されています。
(仕様: https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-02)

_dns.resolver.example.com  7200  IN SVCB 1 . ( alpn=h2 dohpath=/dns-query{?dns} )
_dns.resolver.example.com  7200  IN SVCB 2 . ( alpn=dot )

この情報を見ると、DoHとDoTに対応している事がわかります。このとき、DoHは、path情報も記述されています。

DoHにリクエストを送るときに書きpathでリクエストする必要があることがわかります。(この情報がないと、単純にhttpsで接続してもdohのリクエストができません)
https://resolver.example.com/dns-query{?dns}

ホスト名が分かってない場合

先述の通り、DHCPなどではIPアドレスのみでDNSサーバが指定されます。その場合には「dns://resolver.arpa」に対してSVCBレコードを引きに行きます。
(なお、resolver.arpaはいわゆるSpecial Use Domain Nameとして予約されるドメイン名です)

もし再帰ゾルバがDoHサーバを知っていれば、SVCBレコードを返します。このときipv4hintをつけて返すことで、DoHサーバのAレコード解決を省略することができます。

## 表記上改行
     _dns.example.net  7200  IN SVCB 1 . (
        alpn=h2 dohpath=/dns-query{?dns} ipv4hint=x.y.z.w )

   _dns.example.net  7200  IN SVCB 1 dot.example.net (
         alpn=dot port=8530 ipv4hint=x.y.z.w )

また、このときのサーバ認証については仕様を参照ください。

おわりに

DoHサーバに繋ぐ設定をどうクライアントに入れ込むか、という議論は長らく続いてきました。そのなかで様々な立場から幾つか提案仕様が提出されました。

そんななか出てきたのが今回の仕様です。この仕様はすでにWG Adoptionされているので、この方向で進んでいくのかと思います。

すべてが解決されるわけではないと思いますが、引き続き追っていきたいなと思う。

Webページのサブリソースを一つにまとめる Resource bundles とは

Webページのサブリソースを1つにまとめる 「https://github.com/WICG/resource-bundles=Resource bundles」 という仕組みが検討されている。


[目次]

はじめに

JavaScript, CSS, 画像といったWebページを表示するのに必要なリソースを一つのファイルにまとめるというのは、今でも行われておりwebpack、rollup、Parcel、esbuildといったツールが利用されている。

Resource bundles」の目的を読むと、既存のバンドラーは、個々別にリソースをフェッチするのに比べいくつかのデメリットが有ると述べている。

  • リソース個別のフェッチであれば、MIMEタイプによってはフェッチしながらそのデータの処理を進められるが、それができない
  • リソース個別のフェッチであれば、リソースごとにキャッシュコントロールができる

こういったデメリットを解消しつつ、その他のバンドラーと同様に通信のオーバヘッドを減らしつつ、データの圧縮を行うのが「Resource bundles」 の目的となっている。

2行で説明すると

Resource bundles」 は

  • ファイルを1つにまとめるのではなく、HTTPレスポンスを1つのファイルにまとめる
  • ブラウザによって処理されるので、あたかも個別のHTTPレスポンスを受け取ったかのように処理される

様々な疑問が出てくると思うが、とても長いFAQがあるので見てみるのが良さそう
https://github.com/littledan/resource-bundles/blob/main/faq.md

利用例

利用例を見つつ説明説明していく。
Resource bundles を読み込んで使う例は下記のとおりである

<script type=loadbundle>
{
    "source": "pack.rbn",
    "scope": "static/"
    "paths": {
        "a.js": ["bGpobG", "FzZGZq"],
        "b.js": ["bGpobG", "sbnNkd"],
        "style/page.css": ["a2FzaG"],
    }
}
</script>
<link rel=stylesheet href="static/style/page.css">
<link rel=stylesheet href="static/style/button.css">
<button onclick="import('static/a.js')">a</button>
<button onclick="import('static/b.js')">b</button>
  • <script type=loadbundle> で loadbundle JSON manifestを記述し、読み込む。
  • あとは通常通りHTMLから利用するだけ

loadbundle JSON manifestのpathsに記述した a.js や b.js が必要になったタイミングでResource bundles であるpack.rbnを取りに行く。

それでは、実際に取りに行く流れを確認する。

rbnファイルの取得

ブラウザが a.js が必要になったときに、pack.rbnを取得する流れを確認する

f:id:ASnoKaze:20210209235441p:plain

  • loadbundle JSON manifest部分のpathに、a.jsはチャンクIDとして bGpobG, FzZGZq を持つことが分かる
  • ブラウザはpack.rbnをリクエストする際にヘッダとして「Resource-Bundle-Chunk-Ids: bGpobG FzZGZq」を指定する
  • a.jsが依存している(importしている) common.jsも含めて rbn ファイルがレスポンスとして返される

(rbnファイルは動的に生成される事になるが、その点についてもFAQで補足されている)

取得したrbn ファイルから、各リソースを取り出して利用する。

rbnファイルの中身

それでは、次にResource bundlesファイルの中身を見ていく。
最初に述べたようにResource bundlesには複数のHTTPレスポンスが格納されています。各リソースを取り出すのにrbnファイル全体を読む必要はなく、indexから必要なファイルを探してそこだけ取り出せるようになっている。

なおrbnはCBOR形式で記述されていてる。

具体的なフォーマットは下記のとおりである

resourcebundle = [
  magic: h'F0 9F 8C 90 F0 9F 93 A6',
  version: bytes .size 4,
  section-lengths: bytes .cbor section-lengths,
  sections: [* any ],
  length: bytes .size 8,  ; Big-endian number of bytes in the bundle.
]
section-lengths = [* (section-name: tstr, length: uint) ],

magic, version, section-lengths, 任意数のsections, lengthから構成される

sectionsには、2種類のsectionが任意数入る

  • indexセクション: 各"リソースPATH"から"そのリソースの格納位置(offset)"へのハッシュ
  • resourcesセクション: HTTPレスポンスが入ってる

各レスポンスは、下記の通りヘッダとペイロードのデータが格納されている

response = [headers: bstr .cbor headers, payload: bstr]
headers = {* bstr => bstr}

その他(標準化やツールについて)

W3CのWICGや、IETF WebPackaging WGへの持ち込みが検討されている模様。(追記 2/24に実際にWICGに移管された)

Webページを再配布可能にするWeb Packaging/Web Bundlesと一部競合しそうだが、WebPackagingを進めているGoogleのJeffrey Yasskin氏やYoav Weiss氏とも連携を取っている模様。ココらへんのトピックもFAQで補足されている。

また、実際にこの仕組を扱うには各種ツールが必要なので、それについても紹介されている
https://github.com/littledan/resource-bundles/blob/main/subresource-loading-tools.md

おわりに

実際、サブリソースのローディングに特化しているので、その点は良さそうな気がしている。

が、僕はFrontendに詳しくないのでそっちの方々がどう思うかは聞いてみたい

プライバシーを保護する Oblivious HTTP の仕様

ユーザのトラッキングを防ぐ「Oblivious HTTP」という仕様が、Mozilla及びCloudFlareの方らの共著でIETFに提出されています。この仕様では、ユーザのIPアドレスを隠す仕組みを提供します。これによって、サーバが受信した複数のリクエストが同一ゆーざのものであるかわかりにくします。

先行して、CloudFlareは「Oblivious DoH」という仕組みを提案している。これはDNS over HTTPSのクライアントIPアドレスを隠蔽する仕組みである。その仕組をHTTPに適応したのがOblivious HTTPである。
blog.cloudflare.com

Googleが別途提唱している「Privacy Sandbox」でも、IPアドレスはユーザを識別するための要素であり、対策が検討されています。Privacy Sandboxの中では「Near-Path NAT」という仕組みを提案してる。HTTP/3上で通信をトンネリングする「MASQUE」の利用が検討されている模様。
github.com

今回は、Oblivious HTTPについて簡単に見ていく。

概要

通常のHTTP通信では、サーバ側はクライアントのIPアドレスと、HTTPリクエストの中身の両方を読むことができます。

Oblivious HTTPでは、ProxyはIPアドレスのみ、サーバ側はリクエストの中身のみを取得できます。
(通常Forward Proxyでも同様ですが、コネクションを張りっぱなしにしていると同一ユーザであることは分かってしまいます。)

クライアント・サーバサイドでのサポートが必要ではありますが、TorなどのIPアドレスを隠蔽する方式に比べて軽量であることも謳っています。

登場人物
  • クライアント: HTTPリクエストをOblivious Request Resourceの鍵で暗号化し、カプセル化してOblivious Proxy Resourceに送信します
  • Oblivious Proxy Resource: クライアントから受け取ったカプセル化されたリクエストをそのままOblivious Request Resourceに送信します
  • Oblivious Request Resource: カプセル化されたリクエストから、本来のリクエストを取り出して通常のHTTPリクエストとしてWebサーバ(Oblivious Target Resource)に渡します。

f:id:ASnoKaze:20210207212646p:plain

このように、間に信頼できるProxyサービスが入る形で、ユーザのIPアドレスを隠蔽する。

HTTPリクエストのカプセル化

クライアントは、もともと送りたいHTTPリクエストを暗号化しカプセル化します。

暗号化の処理では、まず
Binary Representation of HTTP Messages」という仕様にのっとりHTTPメッセージをバイナリ表現に変換します。それを、「Hybrid Public Key Encryption (HPKE)」で定義されるように、Oblivious Request Resourceの公開鍵で暗号化します。

クライアントは、暗号化したデータをPOSTリクエストのボディに格納し、Oblivious Proxy Resourceに送信します。

POST /request.example.net/proxy HTTP/1.1
Host: proxy.example.org
Content-Type: message/ohttp-req
Content-Length: 78

<content is the encapsulated request above>

Oblivious Proxy Resourceは受け取ったデータをそのままOblivious Request Resourceに送ります。

POST /oblivious/request HTTP/1.1
Host: example.com
Content-Type: message/ohttp-req
Content-Length: 78

<content is the encapsulated request above>

Oblivious Request Resourceは受け取ったボディから元のリクエストを取り出して、通常通りのHTTPリクエストとしてOblivious Target Resourceが処理します。

わからなかったところ

Oblivious Proxy Resourceがどうやって、転送先のOblivious Request Resourceを選択するのかちょっとわからなかった。

ODoHだと外側のGETリクエストパラメータに転送先が入ってるようだけど、Oblivious HTTPでは...?

NAT Slipstreaming v2 攻撃とブラウザ側の対策

外側から、NAT内の端末の任意ポートにアクセスするNAT Slipstreamingという攻撃があります。

このNAT Slipstreamingについては、以前ブログに書いたとおりです。
asnokaze.hatenablog.com

2021年01月26日に、Armisの研究者らがNAT Slipstreaming v2として新しい攻撃手法を公開しました。

簡単にv1との違いを眺めていこうかと思います。

v2の違い

v2も攻撃の大きな流れは同様です。罠サイトを踏んだNAT内のブラウザに、ALGをご作動させるようなパケットを送らせることで、NATの穴あけを行う。v1はすでにブラウザで対応されていたが、v2ではその対応では不十分だった。

v2の違いを簡単にかいつまむと

  • v1では罠サイトを踏んだ端末のみに外部からアクセス可能だったが、v2では罠サイトを踏んだ端末以外にもアクセス可能

f:id:ASnoKaze:20210129013308p:plain

  • H.323ALGの呼び出し転送機能を用いることで、罠を踏んだ端末以外のIPをNAT外部に穴あけする (呼び出し転送の概略図は下記)

f:id:ASnoKaze:20210129013557p:plain

  • 具体的には罠サイトを踏んだ端末に、ポート1720番宛でH.323パケットに見えるデータを送ると、任意ポートに穴あけできる。

f:id:ASnoKaze:20210129013736p:plain

詳しくは元記事参照のこと。
www.armis.com

ブラウザ側の対応

NAT Slipstreaming攻撃では、罠サイトを踏んだNAT内の端末が特定ポートにHTTPリクエストを送ります。

NAT Slipstreaming v1に対する対策としてブラウザはポート番号5060/5061へのHTTPリクエストをブロックするように変更しました

v2への対策として更に 69, 137, 161, 1719, 1720, 1723, 6566のポートをブロックするように変更されました

ZeroSSL ならIPアドレスのサーバ証明書が取得できる

IPアドレスサーバ証明書が欲しい場合があります。そうすれば、ドメインを取得せずともサーバとHTTPS通信ができるようになります。

その他にも例えばDNS over HTTPSではIPアドレスでアクセス出来るように、有効な証明書がセットされていたりします。

しかし、Let's Encryptでは、IPアドレスサーバ証明書は取得できません

~$ sudo certbot certonly --nginx -d 160.16.124.39
Requested name 160.16.124.39 is an IP address. 
The Let's Encrypt certificateauthority will not issue certificates for a bare IP address.

ZeroSSLでは出来るらしいので試す。

ZeroSSL

下記記事でも紹介されている、無料のサーバ証明書を取得できるZeroSSLではIPアドレス証明書が取得できるようです
zenn.dev

とはいえ特別なことはなく、通常通りアカウントを取得し、ブラウザでIPアドレスを指定してverifyを通せば良いだけです。そうすれば証明書と秘密鍵がダウンロード可能となります。

f:id:ASnoKaze:20210118231726p:plain

設定する

Webサーバに証明書と設定を埋め込めばこの通り

https://160.16.124.39

f:id:ASnoKaze:20210118232153p:plain

おまけ

CAによるIPアドレス証明書の発行に関して

CA/B ForumのBaseline Requirements では、「3.2.2.5 Authentication for an IP Address」に幾つかのverify方法が指定されている。今回は「3.2.2.5.1 Agreed-Upon Change to Website」で記述されている検証方法を使用しており、/.well-known/pki-validationに指定されたファイルを設置することでIPアドレスの所有権が検証されている。

また、同様にCA/B Forumによって「Guidance on IP Addresses in Certificates」というガイドラインも公開されている。

ChromeのPartition Network Stateについて

Chrome 89で「Partition Network State」という機能の導入が検討されています。

chromestatus.com

Client-Side Storage Partitioning 」のいち部であり、w3cのPrivacy CGで議論されています。

これは、DNSキャッシュやHTTP/2のセッションといったネットワーク状態を、クロスサイトで共有できなくする機能です。

情報は多くないようですが、眺めていきます。

背景

ブラウザ内全体でキャッシュ情報を共有していると、サイトAで読み込んだキャッシュを、サイトBを表示する際にも使用できます。

この時サイトBではそのリソースの読み込みスピードを計測することで、キャッシュしてたかそうでないかが分かってしまいます。(読み込み速度はonloadイベント等で取得)

例えば、https://a.example/js/hoge.js といったサイトAに固有なリソースがキャッシュされていれば、ユーザがAにアクセス済みということがわかります。その他にも、特定ページのみ表示される画像なども利用できるでしょう。

こういった問題は以前紹介した「Double-keyed HTTP cache」で対策されています。
Double-keyed HTTP cache に関するメモ - ASnoKaze blog

サイトAで読み込んだキャッシュは、サイトBでは利用できないようになっています。

「Double-keyed HTTP cache」はHTTPのキャッシュを共有しないという対策でしたが、「Partition Network State」はネットワーク状態のキャッシュ共有範囲を制限します。

Partition Network State

Partition Network Stateでは、Double-keyed HTTP cache と同様、top-level siteとiframe siteをキャッシュ共有範囲のキーとして使います。

そのため、クロスサイトではネットワーク状態が共有されません。現在、対象となるネットワーク状態の候補は下記が検討されています。

  • DNSキャッシュ
  • HTTP/1.xのソケット
  • HTTP/2とHTTP/3のセッション (WebSocketを含む)
  • TLSとHTTP/3のセッション再開(session resumption)情報
  • ALT-Svcの情報
  • PAC scriptsからのDNSルックアップ
  • Expect-CT の情報

その他下記についても、Partition Network Stateを尊重するためにさらなる説明(仕様)が必要と述べています

  • HTTP auth cache
  • Client certs
  • Clear-Site-Data header

パフォーマンス

cross-siteのiframe、top-level sitesでネットワーク情報が共有されなくなることによるパフォーマンス影響についても言及されています。

DNSキャッシュの利用率や、TLS/QUICセッション再開の可能性が減ります。また、ALT-Svcの情報も使えないのでHTTP/3で最初から接続できないかもしれません。

小規模な実験では、誤差レベルのパフォーマンス変化しか無いとのことです。改めて規模を大きくしたときの数値については公開するとのことです。

WebTransportの方向性 (2021年1月の中間会議をうけて)

WebTransportと呼ばれる新しい双方向通信の仕組みが議論されています。

WebSocketの次世代版とも呼ばれており、QUICやHTTP/3上で動作します。現在、Chrome ではQUIC上で動作する版の実装が進められていたりします (web.dev)

さて、このWebTransportの標準化は、IETFプロトコルを、W3Cでインターフェースの仕様が議論されています。

W3C

IETF

上記の通り、特にIETFではWebTransportの下位層にどのプロトコルを使うかで幾つかの仕様が提出されています。
f:id:ASnoKaze:20210113024316p:plain

昨年秋に行われたIETF109では、WebTransport WGとしてどの仕様の標準化にフォーカスしていくかという議論がありました。(今の所、どの仕様もWG Itemにはなっていません)

特に、over QUIC と over HTTP/3について、片方のみをまず標準化するのか、両方を一緒にすすめるのかといった論点がありました。しかし、そのミーティング中ではコンセンサスが得られず、メーリングリストで継続して議論となりました。

WebTransport WG 中間会議

1月12日、改めてリモートミーティングの場が設けられました。

ChromeでWebTransportの実装者かつ仕様の著者でもあるVictor Vasiliev氏から、まず現在の仕様の状況について共有がありました。(一旦TCPベースの物は横においておくとのこと)

その後、チェアから参加者に対して質問が投げかけれました。

Question 1: number of protocols
Should the working group adopt only one UDP-based transport?

- 1A: only one transport (QUIC or HTTP/3)
- 1B: multiple transports (QUIC and HTTP/3)

まず幾つのプロトコルをWGとして取り扱っていくかに関して、参加者の多くは 1A、つまり一つであるべきと回答し、コンセンサスが得られました。
(ただし、何かしらの事情により二つ目を標準化することもある)

つづけて、2つ目の質問です

Question 2: UDP-based protocols
Which UDP-based option should we adopt as a starting point for WebTransport protocol?

- 2A: WebTransport over HTTP/3
- 2B: WebTransport over QUIC directly (separate ALPN)

over HTTP/3と over QUICの仕様のどちらをWGとして出発点とするか?参加者の半分以上は2A、つまり over HTTP/3を選択し、こちらもコンセンサスが得られました。また、HTTP/3では、コネクションプーリングは一旦行わない方向となりました。

ミーティング内でのコンセンサスは得られたため、続けてメーリングリストで改めて意見を募る形となりました。

結果及び意見募集についてはチェア氏のメールを参照ください
https://mailarchive.ietf.org/arch/msg/webtransport/9k27ZTwBlg9ncsZQXymg-jAfUOA/

資料

ミーティングではその他にも幾つかの議論があったため、詳細はスライド及び議事録を御覧ください