HTTP Encryption Coding

aes128gcm (内容符号化)

[12] 内容符号化 aes128gcm >>6 2. は、 HTTPメッセージで転送するデータを暗号化するものです。

[182] Web Push で使われています。

仕様書

文脈

[9] HTTPメッセージ (HTTP要求HTTP応答) の内容を暗号化し、適切な鍵を持つ者のみが閲覧可能とし、 ときにサーバーすらその内容を閲覧できなくしたいようなとき、 使えるものです。 >>6 1.

[10] HTTP暗号化といえば TLS があります。 TLSサーバークライアントの間の通信路暗号化するもので、 性質が異なります。 本手法では、サーバーすらデータを読むことができないべきかもしれませんし、 読めないデータを他のサーバーに複製して配置したりすることもあって、 1対の HTTPサーバーHTTPクライアントの間の通信で完結しないかもしれません。 >>6 1.

[135] Web Push で使われます。 アプリケーションサーバーからプッシュサービスへ、そして利用者エージェントへとデータが転送されるので、 本手法により末端対末端暗号化が実現できます。

[136] 理論上他にもいろいろ用途はあり得ますが、実用例があるのか不明です。

[251] 既存手法を流用せず敢えてストリーミング処理に対応させながら (>>11)、 主要な Web Push では単一レコードに制限しているので、 この設計が生きていません。 一体何を想定してこのような仕様にしたのでしょうか。 時代背景的には CDN暗号化した動画データを転送することも想定されていそうですが (実利用事例は不明)、それとて HLS 形式で分割してそれぞれ単一レコードの方が扱いやすそうです。

構文

[65] ある HTTPメッセージにおける aes128gcm で符号化されたデータは、 ヘッダーブロックと、 それに続く1個以上のレコードで構成されます。 >>6 2., >>2.1.

  1. ヘッダーブロック
  2. *
    1. 途中のレコード
  3. 最終レコード

ヘッダーブロック

[45] ヘッダーブロック (header block) (内容符号化ヘッダー (content-coding header) encrypted content coding header >>137 2.) は、 次の4つの引数 (parameter) を順に並べたものです。 >>6 2.1.

salt
内容暗号化鍵を決定するために使うバイト列。 16バイト >>6 2.1.
rs
レコードサイズバイト単位のネットワークバイト順32ビット符号無し整数としたもの。 >>6 2.1.
idlen
keyid 引数長さ8ビット符号無し整数で表したもの。 >>6 2.1.
keyid
使用する IKM を識別するために使える、 idlen 引数で指定された長さバイト列テキストとしてレンダリングする必要がある場合、 RFC 3629 UTF-8 符号化するべきです>>6 2.1. keyid8ビット符号無し整数なので、 長さは [0, 255] バイトです。

[86] ヘッダーブロックは、最短で21バイト、 最長で276バイトです。

[64] 古い I-D では、この情報は Encryption: HTTPヘッダーに入れることになっていました。 HTTPヘッダーではなく本体の先頭に入れることに変更したせいで、 ランダムアクセスできるという特徴が半分嘘になってしまっています。

レコード

[66] レコードは、 暗号文詰め区切子オクテット詰めの順に構成されます。 詰め区切子オクテット (padding delimiter octet) は、 最終レコードでは 0x02、 途中のレコードでは 0x01 です。 詰め (padding) は、 0個以上0x00バイト列です。 >>6 2.

  1. 暗号文
  2. |
    1. 0x01
    2. 0x02
  3. *
    1. 0x00

[17] HTTPメッセージごとに、 レコードサイズ (record size) は固定です。 途中のレコードバイト長は、 必ずレコードサイズです。 最終レコードバイト長は、 レコードサイズ以下です。 >>6 2. 詰めは、 レコードサイズに満たないデータを送るときに適宜挿入するものです。

[252] この任意長の詰めが任意の位置で挿入され得るという性質のせいで、 ランダムアクセスできるという特徴は実効性が怪しくなっています。

[11] 本手法は、 JWE その他各種既存のメッセージベースの暗号化形式を採用せず、 RFC 5116 のより低レベルな構造に依っています。 既存の形式ではストリーミング処理に適しません。 >>6 1. 本手法はレコード構造により、 範囲要求ランダムアクセスが、 レコードサイズ粒度で可能となります。 >>6 2.

[44] 適切なレコードサイズは、状況により異なります。 小さくすると解読済みバイトを早めに解放できるので、 応答性が重要な応用に適しています。 小さいほどランダムアクセス時に解読が必要な余分なデータが少なくて済みます。 逆に大きいほど処理やデータのオーバーヘッドは小さくなるので、 ストリーミング処理ランダムアクセスや任意の詰めが不要な応用は、 レコードサイズを大きくできます。 極端な場合は全体を1レコードにできます。 >>6 2.

[63] レコードサイズを記述する rs 引数は32ビット符号無し整数なので、 AEAD_AES_128_GCM平文の上限 236 - 31 は超え得ません。 >>6 2.1. さらに、 IND-CPA の 2-40 の確率を防ぐため、 同じ IKMsalt で暗号化される平文の総量は 16バイトの 244.5 ブロック未満でなければなりませんレコードサイズが 16バイトの倍数のとき、 これはすなわち詰めとオーバーヘッドを含めて 398テラバイトまで安全に暗号化できることを表します。 16バイトの倍数でないときは更に少なくなり、 最悪ケースは高々74テラバイトになります。 >>6 4.4.

[59] レコードシーケンス番号 (record sequence number) (SEQ) は、 0 から始まる、 ネットワークバイト順の 96ビット符号無し整数です。 >>6 2.3. 最初のレコード0 で、以後 12 と順に増やしていきます 仕様書になし。 96ビット符号無し整数なので、 レコード数は 296 を超えられないことになります。

暗号化と鍵

[13] aes128gcm は、 RFC 5116 5.1 節 AEAD_AES_128_GCM (AES GCM、128ビット内容暗号化鍵利用) で暗号化するものです。 >>6 2.

[15] 暗号化primitive はこの通り固定化されています。 他の手法への対応 (cipher agility) は、別の内容符号化を定義することによって実現し、 折衝Accept-Encoding を使うことになっています。 >>6 2.

[16] こういうとき、これまでの IETFプロトコルだともう1段階抽象化と折衝の仕組みを導入していたところでしょうが、 既存の内容符号化折衝を流用して統合することで、 仕組みが簡単になって、既存の HTTP の実装と統合しやすくなっているのは優れた設計です。

[14] 本手法では、 暗号化時と解読時にが必要となります。 両者で共有する input-keying material (keying materialIKM) と、 HTTPメッセージごとの内容暗号化キー (CEK) の2段階となっています。 これは1つの IKM を複数の HTTPメッセージで再利用するためです >>6 2.2.


[100] 送信時に IKM を取得するには、 次のようにします。

  1. [167] IKM鍵識別子を、を取得した結果に設定します。
  2. [170] IKM鍵識別子を返します。

[72] 受信時に IKM を取得するには、 鍵識別子について、 次のようにします。 >>6 2.1.

  1. [74] IKM を、 鍵識別子についてを取得した結果に設定します。
  2. [219] Assert: IKM は適切な IKM か、失敗
  3. [75] IKM を返します。

[76] の取得の方法は、 仕様書では定義されていません >>6 2.。 受信者はその方法を知っていることが期待されます >>6 2.1.。 取得のために鍵識別子を使うことができます >>6 2.1. (が使わなければならないわけでもありません)。 の共通方法は、応用ごとに別途定めておく必要があります。

[77] 鍵識別子テキストとしてレンダリングする場合の規定がある (>>45) ので、 利用者に提示してを入力させる方式も採り得るようです。 しかしそれも1つの方法に過ぎず、 keyidテキストであるとも保証されていません。一般にはバイト列として扱わなければなりません。

[169] 具体的には Web Push の場合が規定されています (>>142)。


[46] 内容暗号化鍵 (content-encryption key) (CEK) は、 IKMsalt から、 HTTPメッセージごとに求めます。 その方法として、 RFC 5869 HKDFSHA-256 を使ったものを用います。 >>6 2.2.

[89] CEK の取得は、 PRK について次のようにします。 >>6 2.2.

  1. [50] cek_info を、 Content-Encoding: aes128gcm0x00末尾に連結した結果に設定します。
  2. [51] CEK を、 HMAC-SHA-256 (PRK, cek_info0x01末尾に連結した結果) の結果に設定します。

    [53] L = 16 なので、このように簡略化できます。

  3. [52] CEK を返します。

[87] 疑似乱数鍵 (pseudorandom key) (PRK) の取得は、 saltIKM について、 次のようにします。 >>6 2.2.

  1. [49] PRK を、 HMAC-SHA-256 (salt, IKM) の結果に設定します。
  2. [88] PRK を返します。

[90] PRK は、CEKnonce の取得に使います。

[82] salt は、 暗号化時にHTTPメッセージごとに準備し、 ヘッダーブロックに入れて解読者へと引き渡します。 解読者はヘッダーブロックから取り出して使います。

[83] salt の生成は、次のようにします。

  1. [84] salt を、16バイトのバイト列に設定します。
  2. [85] salt を返します。

[54] nonce の取得は、 PRKSEQ について次のようにします。 >>6 2.3.

  1. [55] nonce_info を、 Content-Encoding: nonce0x00末尾に連結した結果に設定します。
  2. [56] NONCE を、 HMAC-SHA-256 (PRK, nonce_info0x01末尾に連結した結果) の結果と SEQXOR した結果に設定します。
  3. [58] Assert: NONCE は96ビットです。
  4. [57] NONCE を返します。

[60] nonce は、 レコードの削除や順序入れ替えを防ぐものです。 >>6 2.3.

Web Push

[146] 利用者エージェントは、 プッシュ購読プッシュ購読の作成時に、 次のようにします。

  1. [147] 鍵ペアを、 P-256 curveECDH 鍵ペアを生成した結果に設定します。 >>137 2., 3.1., >>255
  2. [159] authentication secret を、 16バイトのバイト列に設定します。 >>137 3.2.
  3. [148] プッシュ購読鍵ペアを、鍵ペアに設定します。 >>255
  4. [161] プッシュ購読authentication secret を、 authentication secret に設定します。 >>255

[256] プッシュ購読鍵ペア秘密鍵は、 アプリケーションに提供してはなりません>>255

[257] プッシュ購読鍵ペア公開鍵は、 getKey メソッドを通じてアプリケーションから取得可能です。

[158] symmetric authentication secret である プッシュ購読authentication secret は、 プッシュメッセージが正しく認証されるようにするものです。 >>137 3.2. authentication secret は、 getKey メソッドを通じてアプリケーションから取得可能です。

[254] プッシュ購読の作成から呼び出されます。


[141] プッシュ購読鍵ペア公開鍵プッシュ購読authentication secret は、 他の必要な情報と共に、 アプリケーションに引き渡します >>137 2.アプリケーションは、 これらをアプリケーションサーバーに引き渡します >>137 2.1.

[144] アプリケーションは、 認可された (authorized) アプリケーションサーバーに対して、 認証された秘密が保持される通信媒体を使って、 これらを引き渡さなければなりません。 これは RFC 8030 に述べられた理由に加え、 authentication secret が漏れるとプッシュメッセージを送れてしまうからです。 ほとんどのアプリケーションは予め決められたアプリケーションサーバーとの関係を持っていて、 HTTPS のような方法でこの条件を満たせます。 >>137 2.1.

[145] 一昔前ならこれは保安通信路という言葉で表されていました。 ここでの規定がややもってまわった形になっているのは、 登場するのが1対のクライアントサーバーの間だけで完結するものでないためでしょう。

[162] IKM の取得は、 RFC 5869 HKDFSHA-256 を使ったものを用います。 >>137 3.3. このとき、 アプリケーションサーバー (プッシュメッセージ送信者) は利用者エージェントから引き渡されたプッシュ購読の情報を使い、 利用者エージェント (プッシュメッセージ受信者) は生成して保持していたプッシュ購読の情報を使います。

[142] プッシュ購読における IKM の取得は、 鍵識別子について、 次のようにします。

  1. [149] アプリケーションサーバーの場合、
    1. [150] 鍵ペアを、 P-256 curveECDH 鍵ペアを生成した結果に設定します。 >>137 2., 3.1.

      [152] 鍵ペアは、 プッシュメッセージ暗号化後、 捨てて構いません。 >>137 2.

    2. [151] 鍵識別子を、鍵ペア公開鍵 >>137 2., 3.1., 4.X9.62 uncompressed point form (0x04 で始まる65バイトの列) とします>>137 4.
    3. [220] プッシュ購読鍵ペア公開鍵が適切なでない場合、
      1. [221] 失敗を返し、ここで停止します。
    4. [153] ecdh_secret (ECDH shared secret) を、 ECDH (鍵ペア秘密鍵, プッシュ購読鍵ペア公開鍵) の結果に設定します。 >>137 3.1., 3.4.
  2. [143] 利用者エージェントの場合、
    1. [156] 公開鍵を、鍵識別子X9.62 uncompressed point form公開鍵として解釈した結果に設定します。
    2. [222] 公開鍵が適切なでない場合、
      1. [223] 失敗を返し、ここで停止します。
    3. [155] ecdh_secret (ECDH shared secret) を、 ECDH (プッシュ購読鍵ペア秘密鍵, 公開鍵) の結果に設定します。 >>137 3.1., 3.4.
  3. [166] auth_secret を、 プッシュ購読authentication secret に設定します。 >>137 2., 3.4.
  4. [224] auth_secret が16バイトのバイト列でない場合、
    1. [225] 失敗を返し、ここで停止します。
  5. [168] key_info を、 "WebPush: info" に、 0x00プッシュ購読公開鍵X9.62 uncompressed point form鍵ペア公開鍵X9.62 uncompressed point form、 を順に末尾に追加した結果に設定します。 >>137 3.3., 3.4.
  6. [164] PRK_key を、 HMAC-SHA-256 (auth_secret, ecdh_secret) の結果に設定します。 >>137 3.3., 3.4.
  7. [163] IKM を、 HMAC-SHA-256 (PRK_key, key_info0x01末尾に追加した結果) の結果に設定します。 >>137 3.3., 3.4.
  8. [165] IKM鍵識別子を返します。

[181] 利用者エージェントアプリケーションサーバーは、 受信した公開鍵P-256 curve 上にあることを検証しなければなりません>>137 7.

[250] それ以前の大前提として、公開鍵authentication secret として与えられたものが適切な入力の形であるかも検証しなければなりません。

[258] この検査は Webブラウザー側では subscribe メソッド時点で行われます。

[253] アプリケーションサーバーは、 VAPID で使うと別のを使わなければなりません。 VAPID

符号化

[97] 符号化は、平文バイト列を入力とし、 暗号化されたバイト列を出力とする操作です。 符号化の処理たる符号化器は、 次の状態を保持します。

レコードサイズ
非負整数
PRK
バイト列
CEK
バイト列
SEQ
非負整数

[95] 符号化器による符号化の開始時には、 非負整数レコードサイズについて、 次のようにします。 >>6 2.1.

  1. [67] Assert: レコードサイズは [18, 232 - 1]
  2. [70] salt を、 salt を生成した結果に設定します。
  3. [71] 符号化器レコードサイズを、 レコードサイズに設定します。
  4. [47] IKM鍵識別子を、 送信時の IKM を取得した結果に設定します。
  5. [248] IKM失敗の場合、
    1. [249] 例外投げ、ここで停止します。
  6. [81] Assert: 鍵識別子長さは [0, 255] 仕様書に明記は無し
  7. [43] 符号化器PRK を、 saltIKM について PRK を取得した結果に設定します。
  8. [92] 符号化器CEK を、 符号化器PRK について CEK を取得した結果に設定します。
  9. [48] 符号化器SEQ を、 0 に設定します。
  10. [73] ヘッダーブロックを、 空バイト列に設定します。
  11. [98] ヘッダーブロックに、 salt末尾に追加します。
  12. [99] ヘッダーブロックに、 符号化器レコードサイズネットワークバイト順32ビット符号無し整数としたものを末尾に追加します。
  13. [79] ヘッダーブロックに、 鍵識別子長さ8ビット符号無し整数としたものを末尾に追加します。
  14. [80] ヘッダーブロックに、 鍵識別子末尾に追加します。
  15. [101] ヘッダーブロックを送信します。

[102] 符号化器による符号化の過程、 バイト列データを、 これがバイト列全体の末尾に当たるかどうかを表す真偽値最後について送信するには、 次のようにします。 >>6 2.1.

  1. [103] Assert: データ長さは [0, 符号化器レコードサイズ - 17]
  2. [78] nonce を、 符号化器PRK符号化器SEQ について nonce を取得した結果に設定します。
  3. [27] 暗号文 (ciphertext) を、 AEAD_AES_128_GCM暗号化を実行した結果に設定します。
    符号化器CEK
    nonce
    nonce
    平文
    データ
    関連付けされたデータ
    空バイト列

    [25] RFC 8188 では「additional data」と呼ばれています。

  4. [29] Assert: 暗号文長さ = 平文長さ + 16
  5. [21] 詰め区切子オクテットを、 最後なら 0x02、 それ以外なら 0x01 に設定します。
  6. [20] 符号化器SEQ を、1 インクリメントします。
  7. [19] レコードを、 暗号文詰め区切子オクテット末尾に連結した結果に設定します。
  8. [105] レコード長さレコードサイズ未満の間、繰り返し、
    1. [22] 最後の場合、
      1. [23] ここで繰り返しを脱出して構いません。
    2. [106] レコードに、 0x00末尾に追加します。
  9. [24] レコードを送信します。
  10. [243] 最後の場合、
    1. [244] 送信バイト列を閉じます。

[171] Web Push アプリケーションサーバーは、 プッシュメッセージレコード 1つで暗号化しなければなりませんレコードサイズは、 入力バイト列長さ + 17 以上 (仕様書では greater than) の値としなければなりません>>137 4.

[172] プッシュサービスは、 RFC 8030 7.2節により、 4096バイトを超える payload body に対応する必要はありません。 プッシュ資源 この上限を満たすためには、 ヘッダーブロックが86バイト、 詰め区切子オクテット詰めが1バイト以上AEAD_AES_128_GCM の追加が16バイトなので、 平文に使えるのは高々3993バイトです。 >>137 4.

[175] 対応する必要はない、ということは対応されている可能性があるわけなので、 相互運用性の問題が生じるおそれがあります。

[173] Web Push アプリケーションサーバーは、 aes128gcm 以外の内容符号化を使ってはなりませんaes128gcm を複数回適用することはできません。 >>137 4.

[227] Web Push 用に簡略化された符号化は、 バイト列データを次のようにします。

  1. [228] データ長さが 3933 より大きい場合、
    1. [229] 例外投げ、ここで停止します。
  2. [230] salt を、 salt を生成した結果に設定します。
  3. [231] レコードサイズを、 データ長さ + 17 に設定します。
  4. [232] IKM鍵識別子を、 送信時の IKM を取得した結果に設定します。
  5. [246] IKM失敗の場合、
    1. [247] 例外投げ、ここで停止します。
  6. [237] Assert: 鍵識別子長さ65
  7. [233] PRK を、 saltIKM について PRK を取得した結果に設定します。
  8. [234] CEK を、 PRK について CEK を取得した結果に設定します。
  9. [235] 送信バイト列を、 空バイト列に設定します。
  10. [236] 送信バイト列に、 saltレコードサイズネットワークバイト順32ビット符号無し整数としたもの、 8ビット符号無し整数 65鍵識別子、 を順に末尾に追加します。
  11. [238] nonce を、 PRK0 について nonce を取得した結果に設定します。
  12. [239] 暗号文を、 AEAD_AES_128_GCM暗号化を実行した結果に設定します。
    CEK
    nonce
    nonce
    平文
    データ
    関連付けされたデータ
    空バイト列
  13. [240] 送信バイト列に、 暗号文末尾に追加します。
  14. [241] 送信バイト列に、 0x02末尾に追加します。
  15. [242] 送信バイト列を送信します。
  16. [245] 送信バイト列を閉じます。

復号

[28] 復号は、暗号化されたバイト列を入力とし、 解読されたバイト列を出力とする操作です。 復号の処理たる復号器は、 次の状態を保持します。

レコードサイズ
非負整数
PRK
バイト列
CEK
バイト列
SEQ
非負整数

[26] 復号器による復号は、 真偽値単一レコードについて、 次のようにします。

  1. [130] 結果バイト列を、空バイト列に設定します。
  2. [110] 21バイト受信するのを待ちます。 完了前に入力が終了した場合、 失敗を返してここで停止します。 仕様書になし
  3. [30] ヘッダーブロックを、受信した21バイトのバイト列に設定します。
  4. [31] salt を、ヘッダーブロックの先頭16バイトに設定します。 >>6 2.1.
  5. [104] 復号器レコードサイズを、 ヘッダーブロックの第17バイトから第20バイトをネットワークバイト順32ビット符号無し整数として解釈した結果に設定します。 >>6 2.1.
  6. [112] 復号器レコードサイズ18 未満の場合 >>6 2.1.
    1. [113] 失敗を返し、ここで停止します。 仕様書になし
  7. [107] 識別子長を、 ヘッダーブロックの最終バイトを8ビット符号無し整数として解釈した結果に設定します。 >>6 2.1.
  8. [108] 識別子長バイト受信するのを待ちます。 完了前に入力が終了した場合、 失敗を返してここで停止します。 仕様書になし
  9. [109] 鍵識別子を、 受信した識別子長バイトのバイト列に設定します。
  10. [154] IKM を、 鍵識別子について受信時の IKM を取得した結果に設定します。
  11. [215] IKM失敗の場合、
    1. [216] 失敗を返し、ここで停止します。
  12. [91] 復号器PRK を、 saltIKM について PRK を取得した結果に設定します。
  13. [93] 復号器CEK を、 復号器PRK について CEK を取得した結果に設定します。
  14. [111] 復号器SEQ を、 0 に設定します。
  15. [114] 繰り返し、
    1. [115] レコードを、受信したバイト列に設定します。 レコード長さ復号器レコードサイズに等しくなるか、 受信バイト列の末尾に到達するまでとします。
    2. [120] 詰め区切子オクテットを、 レコードの末尾から順に 0x00 でないバイトを探した結果に設定します。
    3. [121] 詰め区切子オクテットnull の場合、
      1. [122] 失敗を返し、ここで停止します。 >>6 2.
    4. [33] 詰め区切子オクテット0x01 でも 0x02 でもない場合、
      1. [34] 失敗を返し、ここで停止します。 >>6 2.
    5. [123] 詰め区切子オクテット0x01レコード長さ復号器レコードサイズない場合、
      1. [35] この条件は仕様書に明記されていませんが、 最終レコードが 0x02 でなければ失敗という要件が適用されます。
      2. [124] 失敗を返し、ここで停止します。
    6. [179] 単一レコードで、 詰め区切子オクテット0x01 の場合、
      1. [180] 失敗を返し、ここで停止します。 >>137 4.
    7. [125] 暗号文を、 レコードから末尾の 0x00 バイトとその直前の詰め区切子オクテットを除去した結果に設定します。 >>6 2.
    8. [116] nonce を、 復号器PRK復号器SEQ について nonce を取得した結果に設定します。
    9. [94] 平文を、 AEAD_AES_128_GCM解読を実行した結果に設定します。
      復号器CEK
      nonce
      nonce
      暗号文
      暗号文
      関連付けされたデータ
      空バイト列

      [40] RFC 8188 では「additional data」と呼ばれています。

    10. [212] 平文失敗の場合、
      1. [214] 失敗を返し、ここで停止します。 仕様書になし
    11. [29] Assert: 暗号文長さ = 平文長さ + 16
    12. [42] 結果バイト列に、平文末尾に追加します。
    13. [39] 復号器SEQ を、 1 インクリメントします。
    14. [37] 詰め区切子オクテット0x02 の場合、
      1. [126] 次のバイトを受信するか、受信バイト列の終端に到達するのを待ちます。
      2. [127] バイトを受信した場合、
        1. [41] 最終レコードを受信しましたが、まだ次のレコードがあります。
        2. [128] 失敗を返し、ここで停止します。 >>6 2.
      3. [38] ここで停止します。
  16. [117]
    [129] 最終レコードを受信していませんが、もう次のレコードがありません。
  17. [119] 失敗を返し、ここで停止します。 >>6 2.

[131] 結果バイト列復号した結果になります。

[118] 仕様書はエラー処理をほとんど曖昧に濁しています。 レコード失敗とみなすべき条件がいくつか定められていますが、 そのときまでに得られた結果バイト列をそのまま使って良いのかどうか、 失敗したレコードから得られた情報を結果バイト列に含めて良いのか、 仕様書からは読み取れません。

[132] 特に問題なのは、詰め区切子オクテットが最終レコードなら 0x02、 それ以外なら 0x01 でなければ失敗 (fail) としなければならない >>6 2. との条件です。ストリーミング処理でこの要件を満たすためには、 次のレコード (または入力の末端) まで処理を進める必要があります。 仕様書がどうとでも解釈できるので、 実装によって、あるいはネットワークからのバイトの到着タイミング次第で、 挙動が変わってくるおそれがあります。

[62] 途中で途切れたメッセージでも本手法は処理できますが、 完全なメッセージであるものとして処理してはなりません。 途中までのメッセージでも処理する受信者は、 攻撃者によりメッセージが途中で切られた可能性も考慮する必要があります。 >>6 4.2. この規定の存在が、失敗の扱いの解釈を難しくします。 途切れたメッセージランダムアクセスの処理では、 入力中最後のレコード詰め区切子オクテット0x02 でなくても認めなければなりません。 このような実装方針次第でいかようにもなりそうな曖昧な規定方法は、 セキュリティー問題の温床でしかありません。

[157] 途中のレコードで解読に失敗した場合、 無視して次のレコードに進むべきなのか、停止するべきなのか不明です。


[61] 本手法を使って内容の起源を認証する受信者は、 aes128gcm 内容符号化を含まないHTTPメッセージを拒絶しなければなりません内容符号化が自動的に除去されて、最終的な受信者がそれに気づかないおそれがあります。 >>6 4.1.


[174] Web Push 利用者エージェントは、 複数のレコードに対応する必要はありません。 利用者エージェントrs を無視して構いません。 (レコードサイズを検査しなくても、 妥当な場合に解読は高い確率で失敗します。) しかし詰め区切子オクテットは検査しなければなりません0x02 以外の詰め区切子オクテットがある場合、 メッセージを捨てなければなりません>>137 4. つまり単一レコードとして実行しなければなりません。

[177] とひどい規定があります。そんなことが許されるなら、 実際のレコードサイズと矛盾する rs が指定されても受け入れられる可能性と拒絶される可能性があるわけで、 相互運用性のリスクでしかありません。

[178] しかも、複数レコードに対応する必要がないということは、 対応してもいいように聞こえますが、 0x02 かどうか検査しなければならないということは、 複数レコードに対応してはいけないということです。 このような制限が認められることが aes128gcm の仕様書に一言も触れられていないのも問題です。

[176] Web Push では aes128gcm をちょうど1回だけ適用することが求められていますが、 受信者がどうするべきか定められていません。 内容符号化が使われていない場合や、 他の内容符号化が使われている場合にどう処理するべきかは不明です。

[183] Web Push 専用に簡略化した復号は、 次のようにします。

  1. [184] 結果バイト列を、空バイト列に設定します。
  2. [185] 高々4096バイト受信します。
  3. [186] 次に受信したのが受信バイト列の末尾でない場合、
    1. [187] 失敗を返し、ここで停止します。
  4. [188] 受信バイト列を、受信バイト列に設定します。
  5. [189] 受信バイト列長さ86 未満の場合、
    1. [190] 失敗を返し、ここで停止します。
  6. [191] salt を、受信バイト列の先頭16バイトに設定します。
  7. [192] レコードサイズを、 受信バイト列の第17バイトから第20バイトをネットワークバイト順32ビット符号無し整数として解釈した結果に設定します。
  8. [193] レコードサイズ18 未満または受信バイト列バイト長 - 86 未満の場合、
    1. [194] 失敗を返し、ここで停止します。
  9. [195] 識別子長を、 受信バイト列の第21バイトを8ビット符号無し整数として解釈した結果に設定します。
  10. [196] 識別子長65 でない場合、
    1. [197] 失敗を返し、ここで停止します。
  11. [198] 鍵識別子を、 受信バイト列の第22バイトから第86バイトのバイト列に設定します。
  12. [199] IKM を、 鍵識別子について受信時の IKM を取得した結果に設定します。
  13. [217] IKM失敗の場合、
    1. [218] 失敗を返し、ここで停止します。
  14. [200] PRK を、 saltIKM について PRK を取得した結果に設定します。
  15. [201] CEK を、 PRK について CEK を取得した結果に設定します。
  16. [202] 受信バイト列のうち、 第1バイトから第86バイトを除去します。
  17. [203] 受信バイト列から、 末尾の 0x00 をすべて除去します。
  18. [204] 受信バイト列空バイト列の場合、
    1. [205] 失敗を返し、ここで停止します。
  19. [206] 受信バイト列の最後のバイトが 0x02ない場合、
    1. [207] 失敗を返し、ここで停止します。
  20. [208] 受信バイト列から最後のバイトを除去します。
  21. [209] nonce を、 PRK0 について nonce を取得した結果に設定します。
  22. [210] 平文を、 AEAD_AES_128_GCM解読を実行した結果に設定します。
    CEK
    nonce
    nonce
    暗号文
    受信バイト列
    関連付けされたデータ
    空バイト列
  23. [211] Assert: 平文バイト列または失敗
  24. [213] 平文を返します。
[226] こうしてみると、かなり簡略化できたものの実質的な意味のない検査がたくさん残ってしまって、 汎用的な複数レコード対応した手法を1レコードで使ったことの無駄さが感じられます。

歴史

[1] Encrypted Content-Encoding for HTTP ( 版) http://httpwg.org/http-extensions/draft-ietf-httpbis-encryption-encoding.html

[2] draft-ietf-webpush-encryption-08 - Message Encryption for Web Push () https://tools.ietf.org/html/draft-ietf-webpush-encryption-08

[3] IANA登録簿aes128gcm が登録されました。

[4] Add PushManager.supportedContentEncodings (#252) (beverloo著, ) https://github.com/w3c/push-api/commit/813f9af75d59e3fa1522db9aeeaa2bd158ff10bf

[5] encrypted-content-encoding/ece.js at master · web-push-libs/encrypted-content-encoding () https://github.com/web-push-libs/encrypted-content-encoding/blob/master/nodejs/ece.js

[134] 古い案では内容符号化 aesgcm, 内容符号化 aesgcm128, HTTPヘッダー Encryption:, HTTPヘッダー Encryption-Key:, HTTPヘッダー Crypto-Key: が提案されていました。

[8] aes128gcm仕様書IETF提案標準 RFC 8188 が出版されました >>6

[133] それにしてもこの時代に出版されたとは思えない、古き悪しき時代を思わせる曖昧な仕様書です。 構文、生成側処理、解釈側処理のどれなのかはっきりしない曖昧な規定、 事実の文ベースで実装の要件がはっきりしない上に、 用語の表記がその場その場で少しずつ揺れていて意図的かどうかわかりにくい、 挙動が用語のイメージの暗黙の了解によって規定され明文化されていない、 といった IETF にありがちな空気を読まないといけない仕様書。 こんなもので相互運用性は維持できるのでしょうか。

[139] Web Push での暗号化方式の仕様書IETF提案標準 RFC 8291 として出版されました >>137