RFC 9458 Oblivious HTTP の仕組みについて

『Oblivious HTTP』はユーザのプライバシを向上するための技術であり、各ブラウザベンダーおよびCDNベンダーが実装を行っています。

取り組みについては、幾つかの記事があがっています

今回は仕様の観点で、プロトコルの中身に触れていく

背景と目的

通信観点のプライバシーについては、通信の暗号化によりほとんどが保護されています。しかし、幾つか懸念が残っています。

  • IPアドレスは、短期的に同一ユーザを識別するのに使用できる
  • コネクションは、一連の通信が同一ユーザであることを識別するのに使用できます(HTTP/2以降複数のHTTPリクエストにコネクションを再利用する)

例えばあるサービスで、ログアウトして別アカウントとして再ログインすると、サービス側には同じ人物であることが分かってしまいます。
このようなユーザを識別する情報をさらに隠すのが『Oblivious HTTP』の目的です。

(アプリケーションレイヤなど、通信以外のトラッキング手法についてはもちろん守備範囲に入っていません。)

概要

Oblivious HTTPの全体概要は次のとおりです

登場人物
  • Client: Oblivious HTTPの通信の開始者。Target Resourceにアクセスを行いたい者。
  • Relay Resource: Oblivious HTTPの中継者。ClientのIPアドレスは分かるが、HTTPリクエストの中身は分からない。
  • Gateway Resource: Oblivious HTTPの終端者、実際のHTTPリクエストの中身を取り出すことが出来る。ClientのIPアドレスは分からない。
  • Target Resource: Gateway Resourceから実際のHTTPリクエストを受け取り、処理をして、HTTPレスポンスを返す。

ここで、Relay ResourceとGateway Resourceは独立した別の組織によって運営されていることを前提としています。それにより、Oblivious HTTPの仕組みでユーザのプライバシーが保護されます。Relay Resourceは、先述の通りCDNベンダーなどが担っている例があります。

通信の流れ
  • Clientは、あるTarget Resource宛のHTTPリクエストを暗号化するために、別経路で事前にGateway Resourceの鍵情報を取得しておきます (RFC 9458では入手方法は定義されていないが、DNS HTTPSレコードを用いる提案もある <URL>)
  • Clientは、Target Resource宛のHTTPリクエストをまずバイナリ表現にエンコードします。その方法は『RFC 9292 Binary Representation of HTTP Messages』で定義されています。その後、そのデータを暗号化して、Relay ResourceにPOSTします。
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>
  • Relay Resourceは、POSTのデータをそのままGateway ResourceにPOSTする。(Relay Resourceは転送するGateway Resourceが設定されている)
POST /oblivious/request HTTP/1.1
Host: example.com
Content-Type: message/ohttp-req
Content-Length: 78

<content is the Encapsulated Request above>
  • Gateway ResourceはClientからPOSTされたデータを復号し、バイナリ表現からデコードして通常のHTTPリクエストに戻します。それをTarget Resourceに転送します。
  • Target Resourceは、HTTPリクエストを受け取り、処理をして、HTTPレスポンスを返す。

(以下逆向きに処理をしてレスポンスがClientに返る。なおContent-Typeは『message/ohttp-res』になる)

その他のトピック

幾つか仕様内にかかれているトピックを紹介する

パフォーマンス

Oblivious HTTPでは暗号化及び遅延のオーバヘッドがあります。Relay Resourceをクライアントとサーバの間、特にサーバの近くに配置することでオーバヘッドを抑えられると述べている。

リプレイ攻撃対策

Relay ResourceはHTTPリクエストの中身は見えないものの、Gateway Resourceに対してリプレイ攻撃を行うことが出来ます。
カプセル化されたHTTPリクエストの暗号化にしようされたnonceを用いることで、リクエストを一意に識別する方法はあります。

また、カプセル化されたHTTPリクエストにDateヘッダフィールドを追加することが推奨されています。これにより時間を開けて再送信されたものを検知できるようになります。

Relay Resourceの転送先について

Relay Resourceが転送先のGateway Resourceをどう選択するかは、RFC中には書かれていません。
『Oblivious Relay リソースと Oblivious Gateway リソース間の固定の 1 対 1 マッピングを前提としています』とも書かれています。

クライアントやOblivious Relayは事前知識として送信先のURLを分かっている前提になっているのかなと思いました。

また、Oblivious Relayで複数のOblivious Gatewayへの転送をサポートするために、RFC 6570 URI Templateを使うように書かれていますが具体的な使用方法は書かれてなさそうです。

暗号化まわり

カプセル化されたHTTPリクエストの暗号化にはHPKEが使用されます。詳しくはRFCを参照のこと

Key Configuration

HPKE Symmetric Algorithms {
  HPKE KDF ID (16),
  HPKE AEAD ID (16),
}

Key Config {
  Key Identifier (8),
  HPKE KEM ID (16),
  HPKE Public Key (Npk * 8),
  HPKE Symmetric Algorithms Length (16) = 4..65532,
  HPKE Symmetric Algorithms (32) ...,
}

暗号化

hdr = concat(encode(1, key_id),
             encode(2, kem_id),
             encode(2, kdf_id),
             encode(2, aead_id))
info = concat(encode_str("message/bhttp request"),
              encode(1, 0),
              hdr)
enc, sctxt = SetupBaseS(pkR, info)
ct = sctxt.Seal("", request)
enc_request = concat(hdr, enc, ct)

HTTPSレコードの使用

DNS HTTPSレコードを介しOblivious HTTPサポートを示し、Key Config配布する方法については『Discovery of Oblivious Services via Service Binding Records』という提案仕様で記述されています。(このDraftでもRelay Resourceの検出及び設定はスコープ外)

HTTPSレコードの例

svc.example.com. 7200  IN HTTPS 1 . ( alpn=h2 ohttp )

また、Target Resourceは/.well-known/ohttp-gateway からKey Configを提供します

『Retrofit Structured Fields for HTTP』について

IETFのHTTP WGで『Retrofit Structured Fields for HTTP』という提案仕様が出ているので簡単に紹介する

Structured Field Values for HTTP

前提にある「Structured Field Values for HTTP」についてまず触れる。

HTTPでは、HTTPヘッダ(フィールド)の値を構造化データとして扱えるようにする「RFC 8941 Structured Field Values for HTTP」という仕様があります。

その仕様では、ListやDictionaryといったタイプが定義されています。

:

Example-List: sugar, tea, rum
Example-Dict: a=?0, b, c; foo=bar

Structured Field Valuesの目的は主に2つあります

  • 新しいHTTPヘッダを定義する際につどABNFで定義を与えるのではなく、Structured Field ValuesのTypeで構文定義を与えられるようにする
  • パーサーを再利用可能にする

既存のHTTPヘッダに適応することは RFC 8941ではスコープには入っておりませんでした。既存のHTTPヘッダもStructured Field Valuesとしてパースしようというのが『Retrofit Structured Fields for HTTP』になります。

Retrofit Structured Fields for HTTP

『Retrofit Structured Fields for HTTP』では、すでに使用されているHTTPヘッダをStructured Field Valuesとしてパースすることを目的とした仕様です。

戦略は2つあります

  • そのままStructured Field Valuesとしてパースしても問題なさそうなヘッダを指定する
  • それ以外のものは、新しくStructured Field Valuesとしてマップする

具体例を見ると分かりやすいかと思います

Structured Field Valuesとしてパースする

すでに使われているHTTPヘッダでパースできそうなものは、提案仕様のなかでこのように羅列されます。

ただし注意点があり、Structured Field Valuesとして扱う上で次のことを気をつけなければなりません

  • Dictionaryのパラメータキーとして大文字・小文字を区別するケース。クオートのルール
  • 構文エラー時の挙動の違い
  • integerは15桁まで
  • 値が空文字の場合エラーとなる
  • Alt-Svc, Content-Length, Retry-After などは実装や一部互換性の無い値が知られている

Structured Field Valuesとしてマップする

すでに使われているHTTPヘッダでパースできないものは、マップした値をいれられるものとします。

例えば、Dateヘッダは

Date: Sun, 06 Nov 1994 08:49:37 GMT

DateタイプをもつSF-Dateを定義します。

SF-Date: @784111777

このように次のヘッダはマップしたものが定義されます

  • SF-Content-Location (Item)
  • SF-Cookie (List)
  • SF-Date (Item)
  • SF-ETag (Item)
  • SF-Expires (Item)
  • SF-If-Match (List)
  • SF-If-Modified-Since (Item)
  • SF-If-None-Match (List)
  • SF-If-Unmodified-Since (Item)
  • SF-Last-Modified (Item)
  • SF-Location (Item)
  • SF-Referer (Item)
  • SF-Set-Cookie (List)

『Origin-Bound One-Time Codes』の提案仕様

Apple勢から「Origin-Bound One-Time Codes」というSMSで発行するワンタイムコードのフォーマットの提案仕様がIETFに提出されています。

こちらの仕組みの標準化ということで良いかなと思います。
developer.apple.com

背景

Webのログイン時にSMSでワンタイムコードを送信し、入力させることがあります。昨今ではformの 『autocomplete="one-time-code"』属性によりユーザエージェントがSMSのコードを自動入力してくれたりします。

こちらのサイトでも書かれているように、攻撃者がフィッシングの手口で入力させたID/Passを正規サイトにインプットさせるとSMSコードを自動入力させる事ができます。
akaki.io

そこで、SMSで発行したワンタイムコードをドメインに紐付けることでこのような攻撃を防ぐのが今回の目的です

Origin-Bound One-Time Codes

Origin-Bound One-Time Codes」ではSMSやメールで発行するワンタイムコードのフォーマットを定義しています。そこにドメイン名を追加しています。

例としては次のとおりです。747723がexample.comでのみ自動入力されます。

747723 is your ExampleCo authentication code.

@example.com #747723

おまけ

AppleおよびGoogleで実装されているようです。

SMSのワンタイムコードの自動入力が幅広く使われると便利になりますね。
まだ、IETFで議論は具体的には行われてないですが、標準化まで進むと良いなーって思いました

新しいステータスコード『420 Requester Impaired』の提案仕様

新しいHTTPステータスコード"420 Requester Impaired"を定義する『An HTTP Status Code to Report Requester Impairment』という提案仕様が提出されています。

これは、送信者の障害によりサーバは処理を拒否したことを示します。

目的として、重機などの機械やAIシステムが故障してることを示唆することを掲げています。提案仕様のなかでも次のように書かれています。

ある種のAIシステムは、与えられた入力に対して幻覚を見たり、不正確な答えを返したり、異なる答えを返したりといった行動を示すことがある。このようなAIが動作するスピードを考えると、人間の要求者と同様に、AIの要求者の障害を検出することも重要である。
(DeepL)

ステータスコードの定義なのでそのままなんですが、例としては次のとおりです
(ボディは、RFC9457 形式での応答例です)

HTTP/1.1 420 Requester Impaired
Content-Type: application/problem+json
Content-Language: en
{
    "type": "https://example.com/erratic-requests",
    "title": "Requester Impaired: Erratic Behavior Detected.",
    "detail": "Potentially dangerous and erratic requests detected."
}

おまけ

著者によると、元々のアイディアでは、リクエストを送信した人間に問題があるというエイプリルフールネタとして書いていたみたいです。しかし、AIによる現実性があるとして通常の提案仕様として提出したとのことです
( https://lists.w3.org/Archives/Public/ietf-http-wg/2023OctDec/0239.html )

複数レコードタイプを一度に引く仕様『DNS Multiple QTYPEs』

DNS Multiple QTYPEs』という提案仕様がIETFで議論されています。

これは、"A", "AAAA", "HTTPS"など複数のレコードタイプを一度に問い合わせする仕組みを定義しています。

背景: 複数Questionセクションは使えない

もともと、一つのパケットに複数のQuestionセクションを含めることは可能ですが、下記の理由により使用されていません

  • QNAME フィールドが複数あるので、一貫性のあるレスポンスが生成できない
  • RFC1035 などで 多くのケースで Questionセクションが単一であることを暗に述べている
  • 実装がサポートしていない

DNS Multiple QTYPEs

DNS Multiple QTYPEs』では、EDNSオプションを使います。


  • QTD: リクエストでは0, レスポンスでは1。(エコーするサーバを検知するのに使用)
  • QTCOUNT: QT フィールドの数
  • QTn: DNS リソースレコードタイプを指定します


クライアントは

  • EDNSオプションで、QTnに問い合わせしたいレコードタイプを羅列して送信します

サーバはこのリクエストを受信した際

  • [QNAME, QTn, QCLASS]に一致するQuestionに、Answerセクションで回答します
  • サーバもAnswerセクションで回答したものを、EDNSオプションで同様にQTnを指定して応答します (これによりnegative answerを示します)

感想

Happy Eyeball v3みたいなシチュエーションで良さそうだなとは思ったが、DNSサーバ側のキャッシュ状況などによってはすべての回答を準備するのに待ちがあると思うと、準備できたものから回答してほしいかもしれないなあ

QUICのNATトラバーサル用 拡張仕様

IETFに「Using QUIC to traverse NATs」という提案仕様が提出されています。

このDraftでは「WebRTC over QUIC」や「P2P QUIC」のユースケースを想定しています。

大まかな流れとしては

  • 『Proxying Listener UDP in HTTP』といったProxy経由でクライアントはサーバに接続する
  • そのコネクション上でIPアドレスを交換してICE相当の処理を行う
  • QUICのPath validationの仕組みを用いてホールパンチングする
  • コネクションマイグレーションを行う

IETF 118の発表スライドがわかりやすいので、それをもって流れを説明していく

Address Discovery

QUICがProxyを経由してコネクションが確立したあと、サーバから ADD_ADDRESSフレームで候補となるIPアドレス及びポートをクライアントに送信する


Address Matching

ICEと同様にアドレスのマッチングを行う


Traversing the NAT

クライアントはPUNCH_ME_NOW フレームを使用して候補ペアをサーバに送信し、QUICのPath Validationの仕組みでパスプローブを送りホールパンチングします。
Path Validationがうまくいくと、コネクションマイグレーションの仕組みで直通信に切り替えます

QUIC通信を優先する Happy Eyeballs Version 3 の提案

IPv4IPv6デュアルスタック環境において、早く通信確立できた方を使用する『Happy Eyeballs』という仕組みがあります。

RFC 8305 Happy Eyeballs Version 2: Better Connectivity Using Concurrency」においては、次のようにAAAAレコードとAレコードの名前解決を行い早く通信確立できたものを使用するようになっています。

今回、そのVersion 3となる『Happy Eyeballs Version 3: Better Connectivity Using Concurrency』という提案仕様が提出されています。著者はAppleGoogleの方々で、QUIC WGやTLS WGで精力的に活動されている方々です。

Happy Eyeballs Version 3

『Happy Eyeballs Version 3』の大きな特徴は、DNSHTTPSレコードも考慮に入れて接続試行順を決めるというものです。

HTTPSレコード

まずは今回の大きなコンセプトである、HTTPSレコードの説明です。RFCにはなっていませんが「Service binding and parameter specification via the DNS (DNS SVCB and HTTPS RRs)」で定義されており、すでにブラウザやCDNで利用が開始されています。

HTTPSレコードにはHTTPSで接続するときに有用な情報が含まれています

たとえば、cloudflare.com のHTTPSレコードは次のようなデータが入っています。

"1 . alpn=h3,h2 ipv4hint=104.16.132.229,104.16.133.229 ipv6hint=2606:4700::6810:84e5,2606:4700::6810:85e5"
  • alpn: HTTP3対応情報など
  • ipv4hint: ipv4の接続先情報
  • ipv6hint: ipv6の接続先情報
  • ech: TLS Encrypted Client Helloの仕様で要求されるコンフィグ情報
大まかな流れ

Happy Eyeballsは次のような流れで行われます

  • 非同期 DNS クエリの開始
  • 解決された宛先アドレスのソート
  • 非同期接続試行の開始
  • 1 つの接続を確立し、他のすべての試行をキャンセルする
非同期 DNS クエリの開始

HTTPSレコード (SVCB / ) を送信するケースでは、次の順番で送信します

いずれかのDNSレスポンスを受け取った場合、「AAAAとHTTPS両方うけとる」「ipv6hintをもつHTTPSを受け取る」まで一定時間待ち、次のステップに進みます

解決された宛先アドレスのソート

試行する順番として、HTTPSレコードから得られた ECHやQUICサポート情報を優先的に並べることが推奨(SHOULD)されています。

その後、IPファミリとプロトコルをインタリーブさせて、リストを作成します。

接続の試行

リストに従って、順番に接続を試行していきます。(それぞれ「接続試行遅延」は通常250msecですが、最小でも100msecにするように書かれている)

コネクションの確立とは次のタイミングを指す

  • TCPであれば、TCPハンドシェイクの完了
    • なお、TLSハンドシェイク完了までをコネクション確立としてもよい
  • QUICであれば、QUICハンドシェイク完了

その他

今回のdraftには「Supporting IPv6-Only Networks with NAT64 and DNS64」といった項目も書かれているが、、、詳しい人に解説を譲ります、、、

また、個人的には「QUIC IPv6」「QUIC IPv4」の順番のがよさそうだけど、どういう順番でTCP/QUICをソートするのが良いんだろう....

今回はまだdraftが出てきたところで来月のIETF118で議論されることを楽しみにしてます。