NTPを暗号化する Network Time Security for NTP の提案仕様

サーバが正しい時刻に設定されていることは重要であり、時刻同期に使用されるNTPにおいてもサーバの認証や改ざんを防ぎたいというモチベーションは理解できる。

IETFのNTP WGでは、まさにNTPを暗号的に保護するNetwork Time Security for the Network Time Protocol」という仕様策定が進められている。

NTPにはいくつかモードがあるが client-server モードを前提としている。

NTPは詳しくないがざっと見てみる

(正しくはNTPの暗号化ではなく、保護的な感じはある)

目的

Network Time Securityの目的は以下の通り

  • Identity: x.509 証明書で通信相手を確認する
  • Authentication: 時刻同期のパケットを認証し、改ざんされていないことを確認する
  • Confidentiality: 時刻情報に秘匿性はありませんが、NTP拡張フィールドの暗号化をサポートする
  • Replay prevention: 以前のパケットが再送されても検知できるようにする
  • Request-response consistency: クライアントの要求に対する応答で有ることを確かにする
  • Unlinkability: クライアントが別のネットワークに移動して通信を行ったとしても、経路上からは同一クライアントか識別できないようにする
  • Non-amplification: 増幅攻撃が行われないように、リクエストより大きなレスポンスを返さない
  • Scalability: サーバはユーザ固有の状態を保存せず、多くのクライアントにサービスを提供できるようにする

プロトコル概要

このNTSは2つの疎結合のサブプロトコルからなります

暗号化するための鍵を交換するNTS Key Establishment と、NTPv4を暗号化してやりとりするための拡張を定義したNTS Extensions for NTPv4です。

f:id:ASnoKaze:20181005023438p:plain

NTS Key Establishment (NTS-KE)

NTPv4のNTS拡張領域で使用するキーマテリアル(お馴染み RFC5705)を取得するメカニズムです。

TLSを使用して鍵を交換し、クライアントに最初のCookie、NTPサーバのIP等を提供します。また、暗号アルゴリズムなどいくつかのパラメータをネゴシエーションします。

NTS Extensions for NTPv4

NTPv4でNTS用の拡張領域を持ちます。この領域によって正しいサーバと通信できており、時刻情報が改ざんされていないことを確認できるようになります。

具体的には以前に取得したキーマテリアルを使用してNTPv4を暗号的に保護する複数の拡張領域が定義されます。

すべての状態はクライアント側に保持され、サーバはクライアントの状態を保持する必要がないため、スケーラビリティがあります。これらの拡張フィールドには、NTS-KEハンドシェイクから抽出されたキーマテリアルを使用して計算されたCookieと認証タグが含まれます。

NTPサーバはこのクッキーを使用してこのキーマテリアルをリカバリし、認証されたレスポンスを返します。 レスポンスには新しく暗号化されたCookieが含まれます。クライアントは次はこのCookieを使用します。こうすることで、返されたレスポンスは自身のリクエストがあってから生成されたレスポンスであることが確認できます。

実装

すでにいくつかの実装が出ている

その他

時刻同期プロトコルでサーバ認証を行う際、クライアントの時刻が同期されておらずサーバ証明書の検証が失敗される問題が有る。このドキュメントでも完璧な解決策は無いと書かれている。いくつかの緩和策は述べられていますが、やはり本質的な改善では無いようです。

JS Self-Profiling API とは

W3C Web Performance Workingの議事録に「JS Self-Profiling API」についての議論があったので簡単に眺めておく。

ミーティングの「発表スライドはこちら

JS Self-Profiling API

いわゆるRUM(Real user monitoring)などと同様、実際のユーザ側でJavaScriptのプロファイルを取得可能にするというのが「JS Self-Profiling API」のようだ。

f:id:ASnoKaze:20181004002206p:plain

ユーザにより端末やネットワーク環境が違うため、実際のユーザ側でJavaScriptのプロファイルを取りたい。timerを使うことで擬似的に測定はできるが、コード量やオーバヘッドが増える。この提案を行っている、Facebookの人らはJavaScriptとSharedArrayBuffersでサンプリングプロファイラを実装したが正確性とパフォーマンスの欠点があると述べています

といった背景から、このような提案が出ているようだ。

使い方

プロファイルを取るサンプルコードが出ている。
performance.profileでプロファイリングを開始して、stopして得られた結果を送信するような流れである。

const profiler = performance.profile({ categories: ['js'], sampleInterval: 10 });
for (let i = 0; i < 1000000; i++) {
     await doWork();
}
const trace = await profiler.stop();
sendTrace(trace);

profileデータ

得られたデータのフォーマットは、V8のTrace Event FormatGecko profile formatを参考に作られているようだ。

interface ProfilerTrace {
  readonly attribute FrozenArray<ProfilerFrame> frames;
  readonly attribute FrozenArray<ProfilerStack> stacks;
  readonly attribute FrozenArray<ProfilerSample> samples;

  [Default] object toJSON();
};

framesから実行状態を取れる模様

QUICの信頼性のないデータグラム拡張(MESSAGEフレーム/Datagramフレーム)

QUICはUDP上で暗号化された信頼性のあるデータ通信を提供するトランスポートプロトコルです。

現在IETFの「QUIC WG」で標準化が進んでおり、先行して実装されていたGoogle版QUICもIETF版QUICへの移行が進められています。

QUICのアプリケーションレイヤ

現在QUIC上のアプリケーションプロトコルとしては「HTTP over QUIC」にフォーカスして標準化が進められています。

一方で、WebRTC over QUIC (Quartc)、QUIC as a VPN (QBone)、DNS over QUICといったその他のアプリケーションプロトコルでもQUICを使用する考えを持ってる人もいます。

そこでそういったプロトコルのトランスポートとして使用できるように、QUIC上でロスしたパケットに含まれるデータを再送しないデータ通信を可能にする拡張仕様が提案されています。もちろん通信は暗号化されていますし、Ackにより相手が受信したかどうかは確認することが出います。

MESSAGEフレーム/Datagramフレーム

提案自体は同時期に個別に2つの仕様が出ています。

1つ目の「QUIC Messages」はGoogleのIan Swett氏提案しているMESSAGEフレームを定義し利用するもので、Google QUIC v45ですでに実装されています。

2つ目の「An Unreliable Datagram Extension to QUIC」はApple人らによって提案されているDATAGRAMフレームを定義利用するものです。著者はIETF102において、Ian Swett氏との議論に基づいてDraftを書いたと述べています。

これらはフレーム名を除いて同じもののようです。実際に、Ian Swett氏は共同で作業していく意思を示しています。

DATAGRAMフレーム

DATAGRAMフレームのフレームタイプは、0x1c又は0x1dであり最下位ビットが1の場合はLengthフィールドを持ちます。0の場合はパケットの最後まででデータであることを意味します。

DATAGRAMフレームのフォーマットは次のとおりです
f:id:ASnoKaze:20180922223804p:plain

STREAMフレームの場合はストリームIDによって一つのコネクション上に多重化されますが、DATAGRAMフレームの場合は多重化するのならQUICを利用するアプリケーションの責任によって実施されます。

DATAGRAMフレームのみのパケットにはAckする必要がありますが、ロス回復には使用されないためAckを遅延させてバッチ的に応答すべきです(SHOULD)。DATAGRAMフレームはコネクションレベルのフロー制御を受けます。また、輻輳制御やフロー制御によりブロックされた場合はそのままドロップすることもできます(MAY)

ブラウザからシリアルポートにアクセスするSerial API

blink-devメーリングリストに「Intent to Implement: Serial API」として「Serial API」の実装に着手する旨の投稿がされている。

この「Serial API」はW3CのWICGで議論がされており、ブラウザからシリアルポートにアクセス可能にする。3DプリンタやArdbinoなど、様々なデバイスと接続できるようになる。

パーミッションの要求については、すでに実装されている「Web Bluetooth」「WebUSB API」とおなじになるようだ。

sample

仕様では、Ardinoからデータを読み込む例が示されている

//Request the list of ports from the user
SerialPort.requestPorts().then(ports => {

  //Pick the first matching port
  var serial,
      kind = "Arduino",
      key = "manufacturer",
      //find the Arduinos!
      arduinos = ports.filter(port => port.get(key).search(kind) > -1);

  if (arduinos.length) {
     serial = new SerialPort(arduinos[0].path);
     serial.in.read(readData)
  }

  function readData(){
    while(let data = yield serial.read()) {
      console.log(data);
    }
  }
})
.catch(console.error);

Chrome 71で検討されている iframeへのPermission Delegation

Webサイトがカメラや位置情報をアクセスする際にパーミッションを要求する場合がある。iframeで埋め込んだページでも同じようにパーミッション要求が行える。

Chrome 71で導入が検討されている「Permission Delegation」では、iframeで埋め込まれたページでのパーミッション要求の仕組みがわかりやすくなる。(iframe内のドメインからパーミッション要求されても、普通のユーザにはわかりにくい)

iframe内でパーミッションが必要になった際、top level origin(URLバーに表示されているサイト)がパーミッションを要求し、その権限を移譲する形になる。

その結果各パーミッションはtop level originに紐づくため、ユーザは権限の管理が行いやすくなる。

https://asnokaze.com/pd.html は以下のように、iframeを埋め込む。allow属性でiframe内で許可するパーミッションを指定する

<iframe allow="geolocation;camera;" src="https://permission.site">
iframe内でカメラへのアクセスを要求する

  • Chrome 69では、iframe内のドメイン名(permission.site)が要求していると表示される
  • Chrome 71では、のtop level origin(asnokaze.com)が要求していると表示される
パーミッションの削除

Chrome 71では、権限はtop level originに紐付いているので、権限の削除もtop level originから削除すれば良い

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

HTTP/2にはクライアントからHTTPリクエストの優先度を指定することができる。サーバはその優先度に基づいてHTTPレスポンスを返す(無視しても良い)。
一般的にはブラウザ自身が判断し、ページのレンダリングを早くするためにCSSなどは優先度を高く、画像は優先度を低く設定している。

一方で、Webサイトを作る側としては、最初に表示される領域(Above the fold)の画像を優先度高くしてほしいと行ったこともあるだろう。

そこで、HTMLからリクエストの優先度を指示できる「Priority Hints」という仕様の標準化が進められている。

すでに、Chrome Canary (71)で動作するようなので試す。

importanceに"high"や"low"を指定する

     <img src="img/1.png" importance="high">
     <img src="img/2.png" importance="low">
     <img src="img/3.png" importance="low">
     <img src="img/4.png" importance="high">

デベロッパーツールで確認すると
f:id:ASnoKaze:20180912010303p:plain

Priorityの欄が指定したとおりになってる事が確認できる。

仕様

importanceには以下が指定できる

  • high
  • low
  • auto

その他

動作確認はしていないが、その他にもいくつかの使用例が書かれている

preloadでも

HTMLから

<link rel="preload" as="script" href="critical-script.js">
<link rel="preload" as="style" href="theme.css" importance="low" onload="this.rel=stylesheet">

レスポンスヘッダから

Link: </app/style.css>; importance=high
Link: </app/script.js>; importance=low
fetchでも
<script>
 fetch('/api/articles.json', { importance: 'high' }).then(/*...*/)
 fetch('/api/related.json', { importance: 'low' }).then(/*...*/)
</script>

キャッシュがHitした情報を示すCacheヘッダの標準化提案

CDNなどのサービスは、リクエストがキャッシュにヒットしたかどうかをx-cacheヘッダに格納してレスポンスしてくれます

このx-cacheヘッダは独自の形式であり、例えば以下のようになっています。
CDNによっては、x-cache以外のヘッダを使うものもあります)

fastly (document)

X-Cache: MISS, HIT

Amazon CloudFront

X-Cache: Hit from cloudfront

先述のように、x-cacheヘッダは標準化されておらずCDN毎に独自に使用されています。

そこで、IETFのHTTbisにおいて
キャッシュのHit情報などを格納するcacheヘッダの標準化を行う「The Cache HTTP Response Header」という仕様がFastlyのMark Nottingham氏から出されています。

Cacheヘッダ

Cache: HIT_FRESH; node="reverse-proxy.example.com:80";
                    key="https://example.com/foo|Accept-Encoding:gzip",
           HIT_STALE; node="FooCDN parent"; fresh=-45; age=200; latency=3,
           MISS; node="FooCDN edge"; fresh=-45; age=200; latency=98

(シンタックスについては 以前書いた「Structured Headersの記事」を参考に)

リクエストが複数回forwardされた場合は、レスポンスを返す順にカンマ区切りで情報が追記されていく。頭のほうがオリジンに近いほうで、後ろのほうがユーザエージェントに近い。

cache-actions

cache-actionsと呼ばれる、リクエストに対してどういうアクションを取ったかは以下の種類がある

  • HIT_FRESH: リクエストをforwardすることなく、キャッシュとして期限内(fresh)であるものを返した
  • HIT_STALE: リクエストをforwardすることなく、キャッシュとして期限内(fresh)ではないものを返した
  • HIT_REFRESH_MODIFIED: キャッシュが有効ではなかったので、リクエストをforwardして新しく得られたレスポンスを返した
  • HIT_REFRESH_NOT_MODIFIED: キャッシュが有効ではなかったので、リクエストをforwardして有効性が確認できたキャッシュしていたレスポンスを返した
  • HIT_REFRESH_STALE: キャッシュが有効ではなかったので、リクエストをforwardしたが問題が発生したため、保存してあったキャッシュを返した
  • MISS: キャッシュが無かったためリクエストをforwardした
  • MISS_CLIENT: リクエスにCache-Controlヘッダのような、キャッシュが使えないようなパラメータが含まれていたので、リクエストをforwardした
  • BYPASS: レスポンスをキャッシュするように設定されていない
  • ERROR: 保存してあったものか、forwardして得られたキャッシュが使用できなかった
パラメータ

各アクションは下記パラメータを付けることが出来る(OPTIONAL)

  • node: ホスト名やIPアドレスといった、キャッシュノードの識別子
  • fresh: freshさの有効期限([https://tools.ietf.org/html/rfc7234#section-4.2.1:title=[RFC7234], Section 4.2.1])。秒で示され、負の値を取ることも出来る。
  • age: このキャッシュの age を示す([https://tools.ietf.org/html/rfc7234#section-4.2.1:title=[RFC7234], Section 4.2.3])
  • cacheable: このレスポンスをキャッシュとして保存できるかのboolean値
  • key: キャッシュがレスポンスと関連付けられるキー
  • latency: リクエストを受け取ってからレスポンスを返すまでの遅延ミリ秒
  • cl_nm: クライアントへの応答に304 Not Modifiedが含まれるかを示す