Device Memory APIを用いてデバイスのメモリサイズを取得する

背景

Webサービスでは、デバイスの性能毎に軽量版の機能を提供することが有ります。

Google Search、Google Map、Facebookなどではローエンドデバイスでは一部軽量版のページを提供することが有ります。

または、クライアントサイドで何かしらの処理速度などを指標として収集している場合は端末の性能毎に分類・正規化する必要が出てきます。

Device Memory

W3Cでは「Device Memory」という仕様が議論されており、これはデバイスのメモリサイズを取得できる仕組みを提供します。

この仕様では2つの方法が定義されています

なお、サイズはギガバイトで、最も近い0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128のいずれかの値を返す事になっています。

JavaScriptから取得する

JavaScriptから下記で取得できます(httpsの場合のみ)。

navigator.deviceMemory

現在Chrome Canary(64.0.3245.0)では値を取得できます。
f:id:ASnoKaze:20171021022152p:plain

HTTPリクエストヘッダで通知する

もしくはメモリの情報を通知したいデバイスは、HTTPリクエストで通知することもできます。
HTTP Client Hints」の仕様に則り

サーバはレスポンスヘッダで、Accept-CHにDevice-Memoryを設定することで、この値をみて適切なコンテンツを提供できる旨クライアントに通知します。

Accept-CH: Device-Memory
Accept-CH-Lifetime: 86400


クライアントはDevice-Memoryヘッダで、メモリサイズ(単位はギガバイト)をサーバに通知します。

GET /example HTTP/1.1
Device-Memory: 0.75

WebSockets over HTTP2 の提案仕様。再び。

先日「Bootstrapping WebSockets with HTTP/2」という仕様が提出されています。

HTTP/2とWebSockets

HTTP/2の上でWebSocketsを通信することは出来ないのが現状です。

しかし根強くWebSockets over HTTP/2の議論は定期的に行われています。


古くはHTTP/2策定中であった2014年に「WebSocket over HTTP/2」という仕様の提案もありました。これはHTTP/2のレイヤにWebSocketsをマッピングする方式でした。ネゴシーエションやフレーミング、ストリーム管理にも言及しています。しかし、この提案仕様は標準化には至りませんでした。


2016年にも「WiSH: A General Purpose Message Framing over Byte-Stream Oriented Wire Protocols (HTTP)」として、WebSockets互換の拡張仕様が提案されています。こちらに関しては、当時の議事録を見ると「この議論をHTTPbis WGでするのは正しい場所ではない」というコメントが残っています。


毎年行われている、ブラウザベンダ, ミドルウェア実装者, CDN事業者, 大手WebサイトといったHTTPの実装者が議論を行うHTTPWorkshopにおいて、今年は「Future of WebSockets?」という発表があり、Chromeの統計情報とWiSHの紹介があったようです。


そこでWebSocketsについての議論があったようです。

Bootstrapping WebSockets with HTTP/2

そして、昨日MozillaのPatrick McManusから「Bootstrapping WebSockets with HTTP/2」という仕様が出ている。文字通り、HTTP/2上でWebSocketsをブートストラップする仕組みの提案仕様である。


HTTP/2ではCONNECTメソッドを使うことでトンネリングする機能があり(一部今回の仕様で変更する)、それを用いて一つのストリーム上でWebSocketsの通信路を開いてWebSocketsのデータをやりとりする。

CONNECTメソッドを用いた通信路の開設

まずサーバはこの仕様に対応している事を示す、ENABLE_CONNECT_PROTOCOL SETTINGSパラメータを送信する。

クライアントとはCONNECTメソッドと、:protocol疑似ヘッダを用いてWebSockets用の通信路を開設する。この時、通常のCONNECTでは使用できないがこの仕様では:path, および:schemeを指定する必要がある。

サーバのレスポンスを持って通信路が開設される。
f:id:ASnoKaze:20171018003037p:plain

WebSocketsの通信を開始する

その後、開設したストリーム上で、DATAフレームでWebSocketsのアップグレード手順を実施する。
サーバからの101ステータスコードが帰ってきた後、WebSocketsのデータのやりときができるようになる。

f:id:ASnoKaze:20171018003417p:plain

議論

HTTPbis WGのメーリングリストでこのドラフトは議論が始まっています。

疑似ヘッダの話や、upgrade手順の議論、クライアントからCONNECTリクエストとDATAフレームを一度に送れるのではないかといったフィードバックが出ています。

標準化が進むのかはわかりませんが、またしばらくWebSocketsの話は続きそうです。

TLS1.3とDC内での復号に関する熱い議論

各ブラウザや、OpenSSL・BoringSSLといった暗号ライブラリ、ミドルウェアのTLS1.3対応が進んでおり、実際に通信できるところまで来ている。

標準化としても大詰めを迎えている。

前回のIETFより話題に上がっている、TLS1.3に関するDC内での復号を目的とした議論について、新しく「TLS 1.3 Option for Negotiation of Visibility in the Datacenter」という拡張仕様が出ている。


これが割りと盛り上がっているので簡単に整理する

TLS1.3

拡張仕様の話に入る前に、簡単にTLS1.3の現状について触れる。

TLS1.3は、0-RTTハンドシェイクのサポートといったパフォーマンスの改善と、多くのセキュリティ改善が入ったTLSの次期バージョンである。

仕様としても長い間議論されており、draft-21まで出ている。ラストコールまで行ったが、色々の問題で2nd WGLCをやるかと行ったところである。


ただ、一つ大きな課題が残っている。初期より、経路上のファイアウォールなどによりブロックされるという疎通性の問題があり、ハンドシェイクにおけるバージョンネゴシエーションSupported Versions拡張を使用するようになっている。

しかし、それでも疎通性の問題が解決できておらず、前回のIETF99では、1~10%の環境で中間装置の問題で疎通が出来ないケースがあることがわかっている(資料)


疎通性の問題を解決するために、Googleが色々試している。その様子はChrome CanaryのTLS1.3の設定項目を見ると複数の実験バージョンが確認できる。具体的な実験内容は、BoringSSLの該当部分のコードを読む必要がありそうだ。

f:id:ASnoKaze:20171005003240j:plain

おそらく来月開催されるIETF100で、そこら辺の改善についての議論が進むものと思われる。

DC内復号の提案

さて、TLS1.3の標準化が大詰めを迎えるなか、IETFTLS WGでは、TLS1.3のDC内での復号というテーマが出ている。これは、DC内の通信でもTLSを使いながらも、秘密鍵を有していれば任意の通信を復号出来るようにするという要件が特定のエンタープライズ環境であるようで、それを可能にしたいという提案である。

TLS1.3と復号

TLS1.3ではephemeralな鍵交換のみがサポートされており、各セッションごとに暗号化に使用する一時鍵が異なっており、サーバに設定する秘密鍵を有していても復号できなくなっている。


復号しようとするならば、各セッションごとのその一時鍵(セッションシークレット)が必要になる。
最新のopensslであれば -keylogfile を指定することでそれらのパラメータをファイルに残すことも出来る(SSLKEYLOGFILE環境変数を設定することで、各ブラウザでも同様のログを出力可能)

$ openssl s_client -state -connect localhost:443 -keylogfile sslkeylog.log -tls1_3 
//略

$ cat ./sslkeylog.log
# SSL/TLS secrets log file, generated by OpenSSL
SERVER_HANDSHAKE_TRAFFIC_SECRET ce146098355b00754c6fd062f7e0dfdd982bf1b84a51deda78ffe217560852a5 c16440d97afd98db5f2b46c806f342e59e89e5c51c25f89c7614328bd32ab97556ff30c3545dda519fddca59acf95fbf
SERVER_TRAFFIC_SECRET_0 ce146098355b00754c6fd062f7e0dfdd982bf1b84a51deda78ffe217560852a5 0b2b1a6e4648883b0505c17ea6580f4d378a54d468d8b0a5dd07c4867725e1615191d7226d2e87e6f89866daa74ffb91
CLIENT_HANDSHAKE_TRAFFIC_SECRET ce146098355b00754c6fd062f7e0dfdd982bf1b84a51deda78ffe217560852a5 3fd9a41f47028ad33610ee7cbae09120c11b8e64d478ba73745c85078cbdbaa01673ef4589cdf4589c9b2fa22d8ac150
CLIENT_TRAFFIC_SECRET_0 ce146098355b00754c6fd062f7e0dfdd982bf1b84a51deda78ffe217560852a5 f8371e8d335b6e79c7e58be7ee26a6254b848311b320ddb37bc7a8b911ff34f22a812be2dd0e5aaae45208439100eba0

このkeylogfileファイルをWiresharkに設定すれば復号することも出来ます。

ただし、この場合は各サーバが各セッション毎に鍵情報を保管することになり、特定の鍵で任意の通信を復号することは出来ない。

IETF99 (7月)

そのような中で、エンタープライズ環境での影響についてIETF99で発表されたのが下記の資料である。
Impact of TLS 1.3 on Enterprise Network Operations


この発表では合わせて、「Data Center use of Static Diffie-Hellman in TLS 1.3」という、サーバからのkey_shareに静的な鍵を使うようにする提案も行われている。これによって、その鍵を持っていればすべてのセッションを複合できるようになります。

この発表は大変大きな議論を呼び、さらに米国政府関係者が提案を支持したことにもよって、会場のマイクの前には長蛇の列が出来るような状況になりました。もちろん、そのような機能でTLSを弱くすることは出来ないという主張である。


twitter上でも #tlsfight99 ハッシュタグで熱い意見が流れています。
twitter.com

最終的に会場内でこの議論に関する温度感が確認(hum)されましたが、この議論を続けるべきと反対派はだいたい半々でした。

新しい提案

以上の流れが会った上で、DC内で復号出来るようにする拡張仕様が、別の人によって提案がされています
https://tools.ietf.org/html/draft-rhrd-tls-tls13-visibility-00

TLS Visibility Extensionという拡張を定義されており、クライアントとサーバによってハンドシェイク時に合意され、使用されます。

SSWrapDH1とSSWrapDH2という2つの鍵を使用します。

  • SSWrapDH1: 通信を復号するのに使用する秘密鍵とその公開鍵
  • SSWrapDH2: 各セッションごとに使用される秘密鍵とその公開鍵


TLS1.3通信を行う際は

  • SSWrapDH1公開鍵とSSWrapDH2秘密鍵から鍵Keを生成する
  • Keを使用して、TLS 1.3セッションEarly SecretとHandshake Secret(セッションシークレット)を暗号化します。
  • Server HelloメッセージのTLS Visibility Extensionに、SSWrapDH1公開鍵(フィンガープリント)、SSWrapDH2公開鍵、および暗号化されたセッションシークレットの識別子を送信します。


復号する際は

  • TLS Visibility ExtensionからSSWrapDH1公開鍵を取得する
  • SSHWrapDH1秘密鍵とSSWrapDH2公開鍵を使用してKeを生成する
  • Keを使用して、TLS Visibility Extensionで運ばれたセッションシークレットを解読する
  • セッションシークレットを使用して、TLS 1.3セッションの復号化に必要なキーイングマテリアルを導出する


このようにして秘密鍵所持者によって任意の通信を複合出来るようになっている。
他の方法の検討や、Security Considarationsは仕様の中で記述されている。

引き続き来月開催されるIETF100で議論があるかもしれない。
さてさてどうなるのだろうか。

パスワードマネージャが適切にパスワードを生成できるようにするポリシーの提案仕様

パスワードの管理に、1PasswordやLastPassといったパスワードマネージャを使うのは一般的になってきています。

そのようなパスワードマネージャはランダムなパスワードを生成しますが、Webサービスによって使える文字の種類や、長さというのはマチマチです。

そこで、Webサービス側からパスワードの要件について記述できるようにする仕様がIETFに提出されています。

どこのWGで議論するのか、標準化が進むのかよくわからないが、とりあえず読んでおく。

Open Password Automation Recipe Protocol

パスワード自動生成出来るように提案されている仕様は「Open Password Automation Recipe (OPAR) Protocol」というタイトルです。

Webサービスはページ内に下記のようなJSONをOPAR_Policyという変数で埋め込むことで、自サービスのパスワード要件を宣言できます。

例えば以下のとおりである

   {
      "version":1,
      "min_length":8,
      "max_length":20,
      "numbers":{
        "allowed": true,
        "minimum": 2
      },
      "lowercase":{
        "allowed": true,
        "minimum": 2
      },
      "uppercase":{
        "allowed": true,
        "minimum": 2
      },
      "special_characters":{
        "allowed": true,
        "valid_characters": "+-_()*&^%$#@!?",
        "minimum": 2
      },
      "wide_characters":{
        "allowed": false,
        "minimum": 0
      },
      "include_extended_ascii": true
   }

各パラメータは以下のとおりです

  • version: 現状、バージョンは1
  • min_length: パスワードの最小長
  • max_length: パスワードの最大長
  • numbers: 数字を許可するか、および最低文字数を指定
  • lowercase: 小文字を許可するか、および最低文字数を指定
  • uppercase: 大文字を許可するか、および最低文字数を指定
  • special_characters: 記号類を許可するか、許可される記号のリスト、および最低文字数を指定
  • wide_characters: マルチバイト文字を許可するか、および最低文字数を指定
  • include_extended_ascii: キリル文字、アクセント記号付き西洋文字、ギリシャ文字など、拡張ASCII文字セットの文字を許可するか


XSSでこのオブジェクトを上書きされると、本来のポリシーに見合うものの弱いパスワードを発行させられたりするのかな?
と、思ったけどXSSあったらパスワード取られるから自動生成しててもリスクと脅威は変わらないか

キャッシュサーバの効率を改善するHTTP Variantsという提案仕様

HTTP Variants

IETFのHTTP WGやQUIC WGのチェアをしているmnot氏より、キャッシュの効率が改善する「Variants」というHTTPレスポンスヘッダを定義する「HTTP Variants」という提案仕様が出ています。

この機能は、Fastly VCLの機能の標準化のようです。

少々想定している背景がわかりづらいのですが、自分なりに簡単にまとめてみる。

背景

Webにおいて、サーバはクライアントからのリクエストヘッダを見てコンテンツを出し分けています。

例えば、Accept-Languageリクエストヘッダを見てコンテンツの言語を変更しています。キャッシュサーバももちろんこのAccept-Languageを見て、それぞれ毎にコンテンツをキャッシュする必要があります。

次の例を見てみましょう
f:id:ASnoKaze:20171001003447p:plain

  • 1. ブラウザは下記のHTTPリクエストを送信する
   GET /foo HTTP/1.1
   Host: www.example.com
   Accept-Language: en;q=1.0, fr;q=0.5

Accept-Languageヘッダで、英語もしくはフランス語のコンテンツを要求する

  • 2. リクエストを受け付けたオリジンサーバは、下記のHTTPレスポンスを送信する
   HTTP/1.1 200 OK
   Content-Type: text/html
   Content-Language: fr
   Vary: Accept-Language
   Transfer-Encoding: chunked

   [French content]

このオリジンサーバは英語のコンテンツは持っておらず、フランス語のコンテンツを返しています。Content-Languageヘッダでその旨が明示されています。また、Varyヘッダで、キャッシュサーバはAccept-Language毎にコンテンツをキャッシュすべきことを示しています。

問題点

この時の問題は、キャッシュサーバはオリジンサーバが結局どの言語のコンテンツを持っているか知ることが出来ない点です。

日本語もしくはフランス語を要求するユーザがいたらどうでしょうか?

Accept-Language: jp;q=1.0, fr;q=0.5

オリジンサーバが日本語に対応しているかはキャッシュサーバはわかりません。そのため、オリジンサーバはフランス語のコンテンツしか持ってなくても、キャッシュサーバはそのことを知らないので、そのままリクエストをオリジンサーバに転送します。

キャッシュサーバはそのリクエストをオリジンサーバに送った後に、「Content-Language: fr」のHTTPレスポンスヘッダを受け取り、日本語には対応してないことを知ります。キャッシュしてあるフランス語のコンテンツを使えたはずなのに、オリジンサーバに問い合わせしてしまってます。

上記の例は、言語でしたが、Accept-Encodingヘッダでも同様です。

このような手間が発生しないように、オリジンサーバが対応している言語(コンテンツ)をキャッシュサーバに伝達出来るようにするのが「HTTP Variants」の仕組みです。

HTTP Variantsヘッダ

Variantsヘッダは、オリジンサーバがキャッシュサーバに自身がどのようなコンテンツを提供できるか明示できます。

HTTPレスポンスのヘッダに下記のように追加します。

Variants: Content-Language;fr;de

このオリジンサーバは、フランス語とドイツ語が提供できるということを意味しています。このVariantsヘッダが付いたHTTPレスポンスをキャッシュサーバが受けとることで、オリジンサーバが対応しているコンテンツを知ることが出来ます。

つまり、先の例で言えば日本語のコンテンツは提供していないので、日本語のコンテンツが要求されてもキャッシュしてあるフランス語のコンテンツを使えるということです。

以下のように、複数のVariantsを羅列することも出来ます。

   HTTP/1.1 200 OK
   Content-Type: image/gif
   Content-Language: en
   Content-Encoding: br
   Variants: Content-Language;en;jp;de
   Variants: Content-Encoding;br;gzip
   Vary: Accept-Language, Accept-Encoding
   Transfer-Encoding: chunked


このVariantsで指定されたヘッダのセマンティクスを理解しているサーバは、Varyヘッダを無視しなければなりません(MUST)。Variantsヘッダを用いることで、Varyヘッダで指定されたヘッダ毎にキャッシュする必要はなくなります。

HTML要素からHTTP/2優先度を指定する Priority Hints

HTTP/2ではリクエストは並列的に行われますが、クライアントは各リクエストに優先度を設定できます。サーバはこの優先度によって、レスポンスの順番(正確にはレスポンスを返すのに使用するリソース)を、制御しています。

この優先度の設定はブラウザが自動で行っており、Webエンジニアは意識する必要はありません。例えばレンダリングを開始するのに必要なCSSなどは優先度が高く、画像などは優先度が低く設定されるのが一般的です。

しかし、Webエンジニアが明示的にHTML要素にどの優先度でリクエストするか指示したい場合でも現状は出来ません。例えば、ユーザの閲覧目的が特定の画像である場合や、特定のJavaScriptがコンテンツに重要ということもあるかもしれません。

同様にFetch APIにも同じことが言えるかと思います。

そこで、それらを可能にするためにW3CのWICGで「Priority Hints」という、仕組みの議論がされています。

まだCommunity Groupでの議論であり、仕組みが有用か?スコープは?仕組みはどのようにスべきか?などである。たたき台として、例は出てきているが、まだまだ変わる可能性は高い。

Priority Hints

Priority Hintsの仕組みを用いることで、Webデベロッパーは、HTML要素にgroup属性でcritical, fonts, functional, visual, lateを指定することで、優先度を指示できます。

HTTP/2の優先度は、具体的にはWeightとDpendencyからなる。Dependencyを用いることでリソースAをレスポンスしてから、リソースBをレスポンスするように指示できる。上記のgroupはそのまま、HTTP/2のDependencyの関係を指示できるものだと思われる。

現在、たたき台として上がっている分かりやすい例を幾つか示す。

Fetch APIに関してはスコープ内だが、まだ未定。

例1
<img src=foo group=visual>

画像が最初の表示に影響を与えるので比較的高い優先順位でロードされることを意味する。

例2
<img src=foo before=visual>

他の表示に影響を与えるリソースよりも高い優先順位で画像をロードすることを意味する(本当に重要な画像など)。

例3
<link rel=stylesheet href=foo group=late> 

最初の表示をブロックしないスタイルシートであることを意味します。

例4
<iframe src=foo group=late>

iframeとそのすべてのサブリソースの重要度を下げることを意味します。

Open questions

幾つかの課題がOpen questionsと挙げられているが
上記とは別にHTML要素間のDependencyを明示できるようにする仕組みも検討されている。

<img fetch-class="shop-branding" src="logo.png" higher-priority-than="shop-scripts" lower-priority-than="shop-item">

<script fetch-class="shop-scripts" src="lazy-loader.js">

<img fetch-class="shop-item" src="product-01.png">
<img fetch-class="shop-item" src="product-02.png">

DNS Queries over HTTPS の標準化

DNS Queries over HTTPS」の仕様の標準化が始まっている。

今までもこのテーマはIETFにおいて何度か議論になってきましたが、今回 DNS Over HTTPS (doh) WGの設立に合わせて(まだProposed)、この「DNS Queries over HTTPS」の標準化がマイルストーンとなっている。

今回の「DNS Queries over HTTPS」は、Googleの提供しているDNS over HTTPSとは全然違う仕様である点は注意が必要です。GoogleのものはクエリとレスポンスをJson形式で構造化するが、今回は比較的素のDNSクエリをHTTPSで送り合う仕様である(今後JSON形式をサポートする可能性はあります)。


このプロトコルではHTTP/2を使う必要があります(MUST)。HTTP/2を使うことで、既存のWebサーバとのコネクション上でDNSレコードをサーバプッシュで送る事も出来ます。

DNS Queries over HTTPS

背景

たとえば、接続しているネットワークが外のDNSとの通信を許可するとは限りません。

公衆Wi-Fiなどのネットワーク提供者がネットワーク内のDNSとの接続のみを許可していたり、もしくはなりすましされて、DNS通信が偽装される可能性もあります。そのようなネットワークでも安全にDNSとやり取りするために、HTTPS上でDNSクエリを送る方法が今回の「「DNS Queries over HTTPS」」という仕様になります。

HTTPリクエス

GET及びPOSTをサポートします。GETの場合はGETパラメータに、POSTの場合はHTTPボディにDNSクエリが格納されます。
www.example.comのAレコードを問い合わせる場合は

GETの場合

   :method = GET
   :scheme = https
   :authority = dnsserver.example.net
   :path = /.well-known/dns-query?  (no CR)
           content-type=application/dns-udpwireformat&  (no CR)
           body=q80BAAABAAAAAAAAA3d3dwdleGFtcGxlA2NvbQAAAQAB
   accept = application/dns-udpwireformat, application/simpledns+json

POSTの場合

   :method = POST
   :scheme = https
   :authority = dnsserver.example.net
   :path = /.well-known/dns-query
   accept = application/dns-udpwireformat, application/simpledns+json
   content-type = application/dns-udpwireformat
   content-length = 33

   <33 bytes represented by the following hex encoding>
   abcd 0100 0001 0000 0000 0000 0377 7777
   0765 7861 6d70 6c65 0363 6f6d 0000 0100
   01
  • .well-known/dns-queryにリクエストします(別のPathでも可)
  • acceptヘッダにapplication/dns-udpwireformatを設定すべきです
  • GETの場合は、bodyはBase64urlエンコードされます
  • キャッシュ出来るように、DNSクエリのDNS IDは0がセットされる

HTTPレスポンス

HTTPレスポンスボディにDNSレスポンスが格納されます。max-ageにDNSTTLと同じ値を格納すべきです。また、304ステータスコードやLast-Modifiedヘッダ、Etagヘッダは仕様すべきではありません。

www.example.comの問い合わせに、93.184.216.34 (TTL=128)を帰す場合は

   :status = 200
   content-type = application/dns-udpwireformat
   content-length = 64
   cache-control = max-age=128

   <64 bytes represented by the following hex encoding>
   abcd 8180 0001 0001 0000 0000 0377 7777
   0765 7861 6d70 6c65 0363 6f6d 0000 0100

   0103 7777 7707 6578 616d 706c 6503 636f
   6d00 0001 0001 0000 0080 0004 5db8 d822