[12] 
[[内容符号化]] [DFN[[CODE[aes128gcm]]]]
[SRC[>>6 2.]]
は、
[[HTTPメッセージ]]で転送するデータを[[暗号化]]するものです。

[182] [[Web Push]] で使われています。

* 仕様書

[REFS[
- [6] [CITE@en[RFC 8188 - Encrypted Content-Encoding for HTTP]], [TIME[2020-03-09 11:21:54 +09:00]] <https://tools.ietf.org/html/rfc8188>
- [7] [CITE[RFC Errata Report » RFC Editor]], [TIME[2020-03-12 17:34:59 +09:00]] <https://www.rfc-editor.org/errata_search.php?rfc=8188>
- [137] [CITE@en[RFC 8291 - Message Encryption for Web Push]], [TIME[2020-03-09 11:30:01 +09:00]] <https://tools.ietf.org/html/rfc8291>
- [138] [CITE[RFC Errata Report » RFC Editor]], [TIME[2020-03-13 15:40:46 +09:00]] <https://www.rfc-editor.org/errata_search.php?rfc=8291>
- [255] [CITE@en-US[Push API]], [TIME[2020-02-04 16:21:34 +09:00]] <https://w3c.github.io/push-api/#dfn-push-subscription>
]REFS]

* 文脈

[9] 
[[HTTPメッセージ]] ([[HTTP要求]]、
[[HTTP応答]])
の内容を暗号化し、適切な鍵を持つ者のみが閲覧可能とし、
ときに[[サーバー]]すらその内容を閲覧できなくしたいようなとき、
使えるものです。
[SRC[>>6 1.]]

;; [10] [[HTTP]] の[[暗号化]]といえば [[TLS]]
があります。 [[TLS]] は[[サーバー]]と[[クライアント]]の間の[[通信路]]を[[暗号化]]するもので、
性質が異なります。
本手法では、[[サーバー]]すらデータを読むことができないべきかもしれませんし、
読めないデータを他の[[サーバー]]に複製して配置したりすることもあって、
1対の [[HTTPサーバー]]と [[HTTPクライアント]]の間の通信で完結しないかもしれません。
[SRC[>>6 1.]]

[135] 
[[Web Push]] で使われます。
[[アプリケーションサーバー]]から[[プッシュサービス]]へ、そして[[利用者エージェント]]へとデータが転送されるので、
本手法により[[末端対末端]]の[[暗号化]]が実現できます。

[136] 理論上他にもいろいろ用途はあり得ますが、実用例があるのか不明です。

;; [251] 
既存手法を流用せず敢えて[[ストリーミング処理]]に対応させながら
(>>11)、
主要な [[Web Push]] では単一[[レコード]]に制限しているので、
この設計が生きていません。
一体何を想定してこのような仕様にしたのでしょうか。
時代背景的には [[CDN]] で[[暗号化]]した[[動画]]データを転送することも想定されていそうですが
(実利用事例は不明)、それとて [[HLS]] 形式で分割してそれぞれ単一[[レコード]]の方が扱いやすそうです。



* 構文

[65] 
ある
[[HTTPメッセージ]]における
[CODE[aes128gcm]]
で符号化されたデータは、
[[ヘッダーブロック]]と、
それに続く1個以上の[[レコード]]で構成されます。
[SRC[>>6 2., >>2.1.]]

[FIG(railroad)[
= [[ヘッダーブロック]]
=*
== 途中の[[レコード]]
= 最終[[レコード]]
]FIG]

** ヘッダーブロック

[45] 
[DFN[[RUBYB[ヘッダーブロック][header block]]]]
([DFN[[RUBYB[内容符号化ヘッダー][content-coding header]]]]、
[DFN[encrypted content coding header]] [SRC[>>137 2.]])
は、
次の4つの[DFN[[RUBYB[[[引数]]][parameter]]]]を順に並べたものです。
[SRC[>>6 2.1.]]

[FIG(list members)[

: [DFN[[F[[CODE[salt]]]]]] :
[[内容暗号化鍵]]を決定するために使う[[バイト列]]。
16バイト [SRC[>>6 2.1.]]。
: [DFN[[F[[CODE[rs]]]]]] :
[[レコードサイズ]]を[[バイト]]単位の[[ネットワークバイト順]]の[[32ビット符号無し整数]]としたもの。
[SRC[>>6 2.1.]]
: [DFN[[F[[CODE[idlen]]]]]] :
[F[[CODE[keyid]]]]
[[引数]]の[[長さ][バイト長]]を[[8ビット符号無し整数]]で表したもの。
[SRC[>>6 2.1.]]
: [DFN[[F[[CODE[keyid]]]]]] :
使用する [[IKM]] を識別するために使える、
[F[[CODE[idlen]]]]
[[引数]]で指定された[[長さ][バイト長]]の[[バイト列]]。
[[テキスト]]として[[レンダリング]]する必要がある場合、
[[RFC 3629]] [[UTF-8]] 符号化する[SHOULD[べきです]]。
[SRC[>>6 2.1.]]
[F[[CODE[keyid]]]]
が[[8ビット符号無し整数]]なので、
[[長さ][バイト長]]は [0, 255] [[バイト]]です。

]FIG]

[86] 
[[ヘッダーブロック]]は、最短で21バイト、
最長で276バイトです。

[HISTORY[
[64] 
古い [[I-D]] では、この情報は [CODE[Encryption:]]
[[HTTPヘッダー]]に入れることになっていました。
[[HTTPヘッダー]]ではなく[[本体]]の先頭に入れることに変更したせいで、
[[ランダムアクセス]]できるという特徴が半分嘘になってしまっています。
]HISTORY]




** レコード

[66]
[[レコード]]は、
[[暗号文]]、
[[詰め区切子オクテット]]、
[[詰め]]の順に構成されます。
[DFN[[RUBYB[詰め区切子オクテット][padding delimiter octet]]]]は、
最終[[レコード]]では [N[0x02]]、
途中の[[レコード]]では [N[0x01]]
です。
[DFN[[RUBYB[[[詰め]]][padding]]]]は、
0個[[以上]]の
[N[0x00]]
の[[バイト列]]です。
[SRC[>>6 2.]]

[FIG(railroad)[
= [[暗号文]]
= |
== [N[0x01]]
== [N[0x02]]
= *
== [N[0x00]]
]FIG]


[17] 
[[HTTPメッセージ]]ごとに、
[DFN[[RUBYB[レコードサイズ][record size]]]]は固定です。
途中の[[レコード]]の[[バイト長]]は、
必ず[[レコードサイズ]]です。
最終[[レコード]]の[[バイト長]]は、
[[レコードサイズ]][[以下]]です。
[SRC[>>6 2.]]
[[詰め]]は、
[[レコードサイズ]]に満たないデータを送るときに適宜挿入するものです。

;; [252] この任意長の[[詰め]]が任意の位置で挿入され得るという性質のせいで、
[[ランダムアクセス]]できるという特徴は実効性が怪しくなっています。

[11] 
本手法は、
[[JWE]]
その他各種既存の[[メッセージ]]ベースの[[暗号化形式]]を採用せず、
[[RFC 5116]] のより低レベルな構造に依っています。
既存の形式では[[ストリーミング処理]]に適しません。
[SRC[>>6 1.]]
本手法は[[レコード]]構造により、
[[範囲要求]]や[[ランダムアクセス]]が、
[[レコードサイズ]]粒度で可能となります。
[SRC[>>6 2.]]

[44] 
適切な[[レコードサイズ]]は、状況により異なります。
小さくすると解読済み[[バイト]]を早めに解放できるので、
[[応答性]]が重要な[[応用]]に適しています。
小さいほど[[ランダムアクセス]]時に解読が必要な余分なデータが少なくて済みます。
逆に大きいほど処理やデータの[[オーバーヘッド]]は小さくなるので、
[[ストリーミング処理]]や[[ランダムアクセス]]や任意の[[詰め]]が不要な[[応用]]は、
[[レコードサイズ]]を大きくできます。
極端な場合は全体を1レコードにできます。
[SRC[>>6 2.]]

[63] 
[[レコードサイズ]]を記述する
[F[[CODE[rs]]]]
[[引数]]は32ビット[[符号無し整数]]なので、
[CODE[AEAD_AES_128_GCM]] の[VAR[平文]]の上限 2[SUP[36]] - 31 は超え得ません。
[SRC[>>6 2.1.]]
さらに、
[[IND-CPA]] の 2[SUP[-40]] の確率を防ぐため、
同じ [[IKM]] と [F[[CODE[salt]]]]
で暗号化される[VAR[平文]]の総量は
16バイトの
2[SUP[44.5]]
[[ブロック]][[未満]]でなければ[MUST[なりません]]。
[[レコードサイズ]]が
16バイトの[[倍数]]のとき、
これはすなわち[[詰め]]とオーバーヘッドを含めて
398テラバイトまで安全に暗号化できることを表します。
16バイトの倍数でないときは更に少なくなり、
最悪ケースは[[高々]]74テラバイトになります。
[SRC[>>6 4.4.]]

[59] 
[DFN[[RUBYB[レコードシーケンス番号][record sequence number]]]]
([DFN[SEQ]])
は、
[N[0]]
から始まる、
[[ネットワークバイト順]]の
96ビット[[符号無し整数]]です。
[SRC[>>6 2.3.]]
最初の[[レコード]]が [N[0]] で、以後
[N[1]]、[N[2]]
と順に増やしていきます
[SRC[仕様書になし]]。
96ビット[[符号無し整数]]なので、
[[レコード]]数は
2[SUP[96]]
を超えられないことになります。

* 暗号化と鍵

[13] 
[CODE[aes128gcm]]
は、
[[RFC 5116]] 5.1 節
[CODE[AEAD_AES_128_GCM]]
([[AES]] [[GCM]]、128ビット[[内容暗号化鍵]]利用)
で[[暗号化]]するものです。
[SRC[>>6 2.]]

[NOTE[
[15] 
[[暗号化]]の [[primitive]] はこの通り固定化されています。
他の手法への対応 ([[cipher agility]])
は、別の[[内容符号化]]を定義することによって実現し、
[[折衝]]は [CODE[Accept-Encoding]] を使うことになっています。
[SRC[>>6 2.]]

[16] 
こういうとき、これまでの [[IETF]] 
の[[プロトコル]]だともう1段階抽象化と折衝の仕組みを導入していたところでしょうが、
既存の[[内容符号化]]の[[折衝]]を流用して統合することで、
仕組みが簡単になって、既存の [[HTTP]] の実装と統合しやすくなっているのは優れた設計です。
]NOTE]


[14] 
本手法では、
[[暗号化]]時と[[解読]]時に[[鍵]]が必要となります。
両者で共有する
[[input-keying material]]
([[keying material]]、[[IKM]])
と、
[[HTTPメッセージ]]ごとの[[内容暗号化キー]] ([[CEK]])
の2段階となっています。
これは1つの [[IKM]] を複数の [[HTTPメッセージ]]で再利用するためです
[SRC[>>6 2.2.]]。

-*-*-

[100] 
送信時に
[[IKM]] を取得するには、
次のようにします。

[FIG(steps)[
= [167] [VAR[IKM]]、[VAR[鍵識別子]]を、[[鍵]]を取得した結果に設定します。
= [170] [VAR[IKM]] と[VAR[鍵識別子]]を返します。
]FIG]

[72] 
受信時に
[[IKM]] を取得するには、
[VAR[鍵識別子]]について、
次のようにします。
[SRC[>>6 2.1.]]

[FIG(steps)[
= [74] [VAR[IKM]] を、
[VAR[鍵識別子]]について[[鍵]]を取得した結果に設定します。
= [219] [[Assert]]: [VAR[IKM]] は適切な [[IKM]] か、[[失敗]]
= [75] [VAR[IKM]] を返します。
]FIG]

[76] [[鍵]]の取得の方法は、
[[仕様書]]では定義されていません [SRC[>>6 2.]]。 
受信者はその方法を知っていることが期待されます [SRC[>>6 2.1.]]。
取得のために[VAR[鍵識別子]]を使うことができます [SRC[>>6 2.1.]]
(が使わなければならないわけでもありません)。
[[鍵]]の共通方法は、[[応用]]ごとに別途定めておく必要があります。

[77] 
[VAR[鍵識別子]]を[[テキスト]]として[[レンダリング]]する場合の規定がある (>>45)
ので、
[[利用者]]に提示して[[鍵]]を入力させる方式も採り得るようです。
しかしそれも1つの方法に過ぎず、 [CODE[keyid]]
が[[テキスト]]であるとも保証されていません。一般には[[バイト列]]として扱わなければなりません。

[169] 
具体的には [[Web Push]] の場合が規定されています (>>142)。

-*-*-

[46] 
[DFN[[RUBYB[内容暗号化鍵][content-encryption key]]]] ([DFN[[[CEK]]]])
は、
[[IKM]] と [CODE[salt]] から、
[[HTTPメッセージ]]ごとに求めます。
その方法として、
[[RFC 5869]] [[HKDF]]
で
[[SHA-256]] 
を使ったものを用います。
[SRC[>>6 2.2.]]


[89] 
[[CEK]] の取得は、
[VAR[PRK]]
について次のようにします。
[SRC[>>6 2.2.]]

[FIG(steps)[
= [50] 
[VAR[cek_info]]
を、
[CODE[Content-Encoding: aes128gcm]]
に
[N[0x00]]
を[[末尾に連結]]した結果に設定します。
= [51] 
[VAR[CEK]]
を、
[CODE[HMAC-SHA-256]] ([VAR[PRK]], [VAR[cek_info]] に [N[0x01]] を[[末尾に連結]]した結果)
の結果に設定します。
[NOTE[
[53] [VAR[L]] = [N[16]]
なので、このように簡略化できます。
]NOTE]
= [52] 
[VAR[CEK]]
を返します。
]FIG]




[87] 
[DFN[[RUBYB[疑似乱数鍵][pseudorandom key]]]] ([DFN[PRK]])
の取得は、
[VAR[salt]]、[VAR[IKM]] について、
次のようにします。
[SRC[>>6 2.2.]]

[FIG(steps)[
= [49] 
[VAR[PRK]] 
を、
[CODE[HMAC-SHA-256]] ([VAR[salt]], [VAR[IKM]])
の結果に設定します。
= [88] 
[VAR[PRK]] を返します。
]FIG]

[90] [[PRK]] は、[[CEK]] や [[nonce]] の取得に使います。


[82] 
[CODE[salt]]
は、
[[暗号化]]時に[[HTTPメッセージ]]ごとに準備し、
[[ヘッダーブロック]]に入れて[[解読]]者へと引き渡します。
[[解読]]者は[[ヘッダーブロック]]から取り出して使います。

[83] [[salt]] の生成は、次のようにします。

[FIG(steps)[
= [84] [VAR[salt]] を、16バイトの[[バイト列]]に設定します。
=- [68] 
同じ [[IKM]] を使う別の [[HTTPメッセージ]]で同じ 
[VAR[salt]] を使っては[MUST[なりません]]。
[SRC[>>6 2.1., 4.3.]]
=- [69] [[HTTPメッセージ]]ごとに毎回
[VAR[salt]]
を生成する[SHOULD[べきです]]。
[SRC[>>6 4.3.]]
= [85] [VAR[salt]] を返します。
]FIG]


-*-*-


[54] 
[[nonce]]
の取得は、
[VAR[PRK]]、
[VAR[SEQ]]
について次のようにします。
[SRC[>>6 2.3.]]

[FIG(steps)[
= [55] [VAR[nonce_info]] を、
[CODE[Content-Encoding: nonce]]
に
[N[0x00]]
を[[末尾に連結]]した結果に設定します。
= [56] [VAR[NONCE]]
を、
[CODE[HMAC-SHA-256]]
([VAR[PRK]], [VAR[nonce_info]] に [N[0x01]] を[[末尾に連結]]した結果)
の結果と
[VAR[SEQ]]
を
[[XOR]]
した結果に設定します。
= [58] 
[[Assert]]:
[VAR[NONCE]]
は96ビットです。
= [57] [VAR[NONCE]] を返します。
]FIG]


[60] 
[[nonce]]
は、
[[レコード]]の削除や順序入れ替えを防ぐものです。
[SRC[>>6 2.3.]]

** Web Push

[146] [[利用者エージェント]]は、
[[プッシュ購読]][VAR[プッシュ購読]]の作成時に、
次のようにします。

[FIG(steps)[
= [147] [VAR[鍵ペア]]を、 [[P-256 curve]] の [[ECDH]] 
[[鍵ペア]]を生成した結果に設定します。
[SRC[>>137 2., 3.1., >>255]]
= [159] [VAR[authentication secret]]
を、
16バイトの[[バイト列]]に設定します。
[SRC[>>137 3.2.]]
=- [140] 推測し難いものでなければ[MUST[なりません]]。
=- [160] [[RFC 4086]] [[暗号学的に強い乱数生成器]]を使う[SHOULD[べきです]]。
= [148]  
[VAR[プッシュ購読]]の[F[鍵ペア]]を、[VAR[鍵ペア]]に設定します。
[SRC[>>255]]
= [161] [VAR[プッシュ購読]]の [F[authentication secret]]
を、 [VAR[authentication secret]] に設定します。
[SRC[>>255]]
]FIG]

[256] [VAR[プッシュ購読]]の[F[鍵ペア]]の[F[秘密鍵]]は、
[[アプリケーション]]に提供しては[MUST[なりません]]。
[SRC[>>255]]

[257] [VAR[プッシュ購読]]の[F[鍵ペア]]の[F[公開鍵]]は、
[CODE[getKey]] [[メソッド]]を通じて[[アプリケーション]]から取得可能です。

[158] 
[[symmetric authentication secret]]
である
[VAR[プッシュ購読]]の [DFN[[F[authentication secret]]]]
は、
[[プッシュメッセージ]]が正しく[[認証]]されるようにするものです。
[SRC[>>137 3.2.]]
[F[authentication secret]]
は、
[CODE[getKey]] [[メソッド]]を通じて[[アプリケーション]]から取得可能です。

[254] [[プッシュ購読の作成]]から呼び出されます。

-*-*-

[141] 
[VAR[プッシュ購読]]の[F[鍵ペア]]の[F[公開鍵]]と
[VAR[プッシュ購読]]の [F[authentication secret]]
は、
他の必要な情報と共に、
[[アプリケーション]]に引き渡します
[SRC[>>137 2.]]。
[[アプリケーション]]は、
これらを[[アプリケーションサーバー]]に引き渡します
[SRC[>>137 2.1.]]。

[144] 
[[アプリケーション]]は、
[RUBYB[認可された][authorized]][[アプリケーションサーバー]]に対して、
[[認証]]された秘密が保持される通信媒体を使って、
これらを引き渡さなければ[MUST[なりません]]。
これは [[RFC 8030]] に述べられた理由に加え、
[[authentication secret]]
が漏れると[[プッシュメッセージ]]を送れてしまうからです。
ほとんどの[[アプリケーション]]は予め決められた[[アプリケーションサーバー]]との関係を持っていて、
[[HTTPS]] のような方法でこの条件を満たせます。
[SRC[>>137 2.1.]]

;; [145] 一昔前ならこれは[[保安通信路]]という言葉で表されていました。
ここでの規定がややもってまわった形になっているのは、
登場するのが1対の[[クライアント]]と[[サーバー]]の間だけで完結するものでないためでしょう。

[162] 
[[IKM]] の取得は、
[[RFC 5869]] [[HKDF]]
で
[[SHA-256]] 
を使ったものを用います。
[SRC[>>137 3.3.]]
このとき、
[[アプリケーションサーバー]] ([[プッシュメッセージ]]送信者)
は[[利用者エージェント]]から引き渡された[VAR[プッシュ購読]]の情報を使い、
[[利用者エージェント]] ([[プッシュメッセージ]]受信者)
は生成して保持していた[VAR[プッシュ購読]]の情報を使います。


[142] 
[VAR[プッシュ購読]]における
[[IKM]]
の取得は、
[VAR[鍵識別子]]について、
次のようにします。

[FIG(steps)[
= [149] [[アプリケーションサーバー]]の場合、
== [150] [VAR[鍵ペア]]を、 [[P-256 curve]] の [[ECDH]] 
[[鍵ペア]]を生成した結果に設定します。
[SRC[>>137 2., 3.1.]]
[NOTE[
[152] 
[VAR[鍵ペア]]は、
[[プッシュメッセージ]]の[[暗号化]]後、
捨てて構いません。
[SRC[>>137 2.]]
]NOTE]
== [151] [VAR[鍵識別子]]を、[VAR[鍵ペア]]の[F[公開鍵]]
[SRC[>>137 2., 3.1., 4.]]
を [[X9.62]] [[uncompressed point form]]
([N[0x04]] で始まる65バイトの列)
と[MUST[します]]。
[SRC[>>137 4.]]
== [220] 
[VAR[プッシュ購読]]の[F[鍵ペア]]の[F[公開鍵]]が適切な[[鍵]]でない場合、
=== [221] [[失敗]]を返し、ここで停止します。
== [153] 
[VAR[ecdh_secret]]
(ECDH shared secret) を、
[[ECDH]]
([VAR[鍵ペア]]の[F[秘密鍵]],
[VAR[プッシュ購読]]の[F[鍵ペア]]の[F[公開鍵]])
の結果に設定します。
[SRC[>>137 3.1., 3.4.]]
= [143] [[利用者エージェント]]の場合、
== [156] [VAR[公開鍵]]を、[VAR[鍵識別子]]を
[[X9.62]] [[uncompressed point form]]
の[[公開鍵]]として解釈した結果に設定します。
== [222] 
[VAR[公開鍵]]が適切な[[鍵]]でない場合、
=== [223] [[失敗]]を返し、ここで停止します。
== [155] 
[VAR[ecdh_secret]]
(ECDH shared secret) を、
[[ECDH]]
([VAR[プッシュ購読]]の[F[鍵ペア]]の[F[秘密鍵]],
[VAR[公開鍵]])
の結果に設定します。
[SRC[>>137 3.1., 3.4.]]
= [166] 
[VAR[auth_secret]] を、
[VAR[プッシュ購読]]の [F[authentication secret]] 
に設定します。
[SRC[>>137 2., 3.4.]]
= [224] 
[VAR[auth_secret]] 
が16バイトの[[バイト列]]でない場合、
== [225] [[失敗]]を返し、ここで停止します。
= [168] 
[VAR[key_info]]
を、
"WebPush: info"
に、
[N[0x00]]、
[VAR[プッシュ購読]]の[F[公開鍵]]の [[X9.62]] [[uncompressed point form]]、
[VAR[鍵ペア]]の[F[公開鍵]]の [[X9.62]] [[uncompressed point form]]、
を順に[[末尾に追加]]した結果に設定します。
[SRC[>>137 3.3., 3.4.]]
= [164] 
[VAR[PRK_key]] を、
[[HMAC-SHA-256]] ([VAR[auth_secret]], [VAR[ecdh_secret]])
の結果に設定します。
[SRC[>>137 3.3., 3.4.]]
= [163] 
[VAR[IKM]] を、
[[HMAC-SHA-256]] ([VAR[PRK_key]], [VAR[key_info]] に [N[0x01]]
を[[末尾に追加]]した結果)
の結果に設定します。
[SRC[>>137 3.3., 3.4.]]
= [165] 
[VAR[IKM]]
と[VAR[鍵識別子]]を返します。
]FIG]

[181] 
[[利用者エージェント]]や[[アプリケーションサーバー]]は、
受信した[[公開鍵]]が [[P-256 curve]]
上にあることを検証しなければ[MUST[なりません]]。
[SRC[>>137 7.]]

[250] それ以前の大前提として、[[公開鍵]]や
[[authentication secret]]
として与えられたものが適切な入力の形であるかも検証しなければなりません。

[258] この検査は
[[Webブラウザー]]側では [CODE[subscribe]] [[メソッド]]時点で行われます。

[253] 
[[アプリケーションサーバー]]は、
[[VAPID]] で使う[[鍵]]と別の[[鍵]]を使わなければなりません。
[SEE[ [[VAPID]] ]]

* 符号化


[97] 
[[符号化]]は、[[平文]]の[[バイト列]]を入力とし、
[[暗号化]]された[[バイト列]]を出力とする操作です。
[[符号化]]の処理たる[VAR[符号化器]]は、
次の状態を保持します。

[FIG(list members)[
: [F[レコードサイズ]] : [[非負整数]]。
: [F[PRK]] : [[バイト列]]。
: [F[CEK]] : [[バイト列]]。
: [F[SEQ]] : [[非負整数]]。
]FIG]

[95] 
[VAR[符号化器]]による[[符号化]]の開始時には、
[[非負整数]][VAR[レコードサイズ]]について、
次のようにします。
[SRC[>>6 2.1.]]

[FIG(steps)[
= [67] [[Assert]]: [VAR[レコードサイズ]]は [18, 2[SUP[32]] - 1]
= [70] [VAR[salt]] を、
[[salt]] を生成した結果に設定します。
= [71] [VAR[符号化器]]の[F[レコードサイズ]]を、
[VAR[レコードサイズ]]に設定します。
= [47] [VAR[IKM]]、[VAR[鍵識別子]]を、
送信時の
[[IKM]]
を取得した結果に設定します。
= [248] [VAR[IKM]] が[[失敗]]の場合、
== [249] [[例外]]を[[投げ]]、ここで停止します。
= [81] [[Assert]]: [VAR[鍵識別子]]の[[長さ][バイト長]]は [0, 255]
[SRC[仕様書に明記は無し]]
= [43] 
[VAR[符号化器]]の [F[PRK]]
を、
[VAR[salt]]、[VAR[IKM]] について
[[PRK]]
を取得した結果に設定します。
= [92] [VAR[符号化器]]の [F[CEK]]
を、
[VAR[符号化器]]の [F[PRK]]
について
[[CEK]] を取得した結果に設定します。
= [48] [VAR[符号化器]]の [F[SEQ]] を、 [N[0]] に設定します。
= [73] [VAR[ヘッダーブロック]]を、
[[空バイト列]]に設定します。
= [98] [VAR[ヘッダーブロック]]に、
[VAR[salt]] を[[末尾に追加]]します。
= [99] [VAR[ヘッダーブロック]]に、
[VAR[符号化器]]の[F[レコードサイズ]]を[[ネットワークバイト順]]の[[32ビット符号無し整数]]としたものを[[末尾に追加]]します。
= [79] [VAR[ヘッダーブロック]]に、
[VAR[鍵識別子]]の[[長さ][バイト長]]を[[8ビット符号無し整数]]としたものを[[末尾に追加]]します。
= [80] [VAR[ヘッダーブロック]]に、
[VAR[鍵識別子]]を[[末尾に追加]]します。
= [101] [VAR[ヘッダーブロック]]を送信します。
]FIG]

[102] 
[VAR[符号化器]]による[[符号化]]の過程、
[[バイト列]][VAR[データ]]を、
これが[[バイト列]]全体の末尾に当たるかどうかを表す[[真偽値]][VAR[最後]]について送信するには、
次のようにします。
[SRC[>>6 2.1.]]

[FIG(steps)[
= [103] [[Assert]]: [VAR[データ]]の[[長さ][バイト長]]は
[0, [VAR[符号化器]]の[F[レコードサイズ]] - 17]
= [78] 
[VAR[nonce]]
を、
[VAR[符号化器]]の [F[PRK]] 
と[VAR[符号化器]]の [F[SEQ]]
について
[[nonce]] を取得した結果に設定します。
= [27] 
[VAR[[RUBYB[暗号文][ciphertext]]]]を、
[CODE[AEAD_AES_128_GCM]]
の[[暗号化]]を実行した結果に設定します。
[FIG(list members)[
: [VAR[鍵]] : [VAR[符号化器]]の [F[CEK]]
: [VAR[nonce]] : [VAR[nonce]]
: [VAR[平文]] : [VAR[データ]]
: [VAR[関連付けされたデータ]] : [[空バイト列]]
[NOTE[
[25] [[RFC 8188]] では「additional data」と呼ばれています。
]NOTE]
]FIG]
= [29] [[Assert]]:
[VAR[暗号文]]の[[長さ][byte length]] = [VAR[平文]]の[[長さ][byte length]] + [N[16]]
= [21] [VAR[詰め区切子オクテット]]を、
[VAR[最後]]なら 
[N[0x02]]、
それ以外なら
[N[0x01]]
に設定します。
= [20] [VAR[符号化器]]の [F[SEQ]] を、[N[1]] [[インクリメント]]します。
= [19] [VAR[レコード]]を、
[VAR[暗号文]]に[VAR[詰め区切子オクテット]]を[[末尾に連結]]した結果に設定します。
= [105] [VAR[レコード]]の[[長さ][バイト長]]が[VAR[レコードサイズ]][[未満]]の間、繰り返し、
== [22] [VAR[最後]]の場合、
=== [23] ここで繰り返しを脱出して構いません。
== [106] [VAR[レコード]]に、 [N[0x00]] を[[末尾に追加]]します。
= [24] [VAR[レコード]]を送信します。
= [243] [VAR[最後]]の場合、
== [244] 送信バイト列を閉じます。
]FIG]

-*-*-

[171] 
[[Web Push]]
[[アプリケーションサーバー]]は、
[[プッシュメッセージ]]を[[レコード]]
1つで[[暗号化]]しなければ[MUST[なりません]]。
[[レコードサイズ]]は、
入力[[バイト列]]の[[長さ][バイト長]] + [N[17]]
[[以上]]
([[仕様書]]では [[greater than]])
の値としなければ[MUST[なりません]]。
[SRC[>>137 4.]]

[172] 
[[プッシュサービス]]は、
[[RFC 8030]] 7.2節により、
4096バイトを[[超える]] [[payload body]]
に対応する必要はありません。
[SEE[ [[プッシュ資源]] ]]
この上限を満たすためには、
[[ヘッダーブロック]]が86バイト、
[[詰め区切子オクテット]]と[[詰め]]が1バイト[[以上]]、
[CODE[AEAD_AES_128_GCM]] の追加が16バイトなので、
[[平文]]に使えるのは高々3993バイトです。
[SRC[>>137 4.]]

;; [175] 対応する必要はない、ということは対応されている可能性があるわけなので、
[[相互運用性]]の問題が生じるおそれがあります。

[173] 
[[Web Push]]
[[アプリケーションサーバー]]は、
[CODE[aes128gcm]]
以外の[[内容符号化]]を使っては[MUST[なりません]]。
[CODE[aes128gcm]] を複数回適用することはできません。
[SRC[>>137 4.]]


[227] 
[[Web Push]] 用に簡略化された[[符号化]]は、
[[バイト列]][VAR[データ]]を次のようにします。

[FIG(steps)[
= [228] [VAR[データ]]の[[長さ][バイト長]]が 3933
より大きい場合、
== [229] [[例外]]を[[投げ]]、ここで停止します。
= [230] [VAR[salt]] を、
[[salt]] を生成した結果に設定します。
= [231] [VAR[レコードサイズ]]を、
[VAR[データ]]の[[長さ][バイト長]] + [N[17]]
に設定します。
= [232] [VAR[IKM]]、[VAR[鍵識別子]]を、
送信時の
[[IKM]]
を取得した結果に設定します。
= [246] [VAR[IKM]] が[[失敗]]の場合、
== [247] [[例外]]を[[投げ]]、ここで停止します。
= [237] [[Assert]]: [VAR[鍵識別子]]の[[長さ][バイト長]]は [N[65]]。
= [233] [VAR[PRK]]
を、
[VAR[salt]]、[VAR[IKM]] について
[[PRK]]
を取得した結果に設定します。
= [234] [VAR[CEK]]
を、
[VAR[PRK]]
について
[[CEK]] を取得した結果に設定します。
= [235] [VAR[送信バイト列]]を、
[[空バイト列]]に設定します。
= [236] [VAR[送信バイト列]]に、
[VAR[salt]]、
[VAR[レコードサイズ]]を[[ネットワークバイト順]]の[[32ビット符号無し整数]]としたもの、
[[8ビット符号無し整数]] [N[65]]、
[VAR[鍵識別子]]、
を順に[[末尾に追加]]します。
= [238]  
[VAR[nonce]]
を、
[VAR[PRK]] 
と [N[0]]
について
[[nonce]] を取得した結果に設定します。
= [239] 
[VAR[暗号文]]を、
[CODE[AEAD_AES_128_GCM]]
の[[暗号化]]を実行した結果に設定します。
[FIG(list members)[
: [VAR[鍵]] : [VAR[CEK]]
: [VAR[nonce]] : [VAR[nonce]]
: [VAR[平文]] : [VAR[データ]]
: [VAR[関連付けされたデータ]] : [[空バイト列]]
]FIG]
= [240] [VAR[送信バイト列]]に、
[VAR[暗号文]]を[[末尾に追加]]します。
= [241] [VAR[送信バイト列]]に、 [N[0x02]] を[[末尾に追加]]します。
= [242] [VAR[送信バイト列]]を送信します。
= [245] 送信バイト列を閉じます。
]FIG]


* 復号

[28] 
[[復号]]は、[[暗号化]]された[[バイト列]]を入力とし、
[[解読]]された[[バイト列]]を出力とする操作です。
[[復号]]の処理たる[VAR[復号器]]は、
次の状態を保持します。

[FIG(list members)[
: [F[レコードサイズ]] : [[非負整数]]。
: [F[PRK]] : [[バイト列]]。
: [F[CEK]] : [[バイト列]]。
: [F[SEQ]] : [[非負整数]]。
]FIG]


[26] 
[VAR[復号器]]による[[復号]]は、
[[真偽値]][VAR[単一レコード]]について、
次のようにします。

[FIG(steps)[
= [130] [VAR[結果バイト列]]を、[[空バイト列]]に設定します。
= [110] 21バイト受信するのを待ちます。
完了前に入力が終了した場合、
[[失敗]]を返してここで停止します。
[SRC[仕様書になし]]
= [30] [VAR[ヘッダーブロック]]を、受信した21バイトの[[バイト列]]に設定します。
= [31] [VAR[salt]]
を、[VAR[ヘッダーブロック]]の先頭16バイトに設定します。
[SRC[>>6 2.1.]]
= [104] [VAR[復号器]]の[F[レコードサイズ]]を、
[VAR[ヘッダーブロック]]の第17バイトから第20バイトを[[ネットワークバイト順]]の[[32ビット符号無し整数]]として解釈した結果に設定します。
[SRC[>>6 2.1.]]
= [112] [VAR[復号器]]の[F[レコードサイズ]]が [N[18]] [[未満]]の場合 [SRC[>>6 2.1.]]、
== [113] [[失敗]]を返し、ここで停止します。 [SRC[仕様書になし]]
= [107] [VAR[識別子長]]を、
[VAR[ヘッダーブロック]]の最終バイトを[[8ビット符号無し整数]]として解釈した結果に設定します。
[SRC[>>6 2.1.]]
= [108] [VAR[識別子長]]バイト受信するのを待ちます。
完了前に入力が終了した場合、
[[失敗]]を返してここで停止します。
[SRC[仕様書になし]]
= [109] [VAR[鍵識別子]]を、
受信した[VAR[識別子長]]バイトの[[バイト列]]に設定します。
= [154] [VAR[IKM]] を、
[VAR[鍵識別子]]について受信時の
[[IKM]]
を取得した結果に設定します。
= [215] [VAR[IKM]] が[[失敗]]の場合、
== [216] [[失敗]]を返し、ここで停止します。
= [91] [VAR[復号器]]の [F[PRK]] を、
[VAR[salt]]、
[VAR[IKM]] について
[[PRK]] を取得した結果に設定します。
= [93] [VAR[復号器]]の [F[CEK]] を、
[VAR[復号器]] の [F[PRK]]
について
[[CEK]] を取得した結果に設定します。
= [111] [VAR[復号器]]の [F[SEQ]] を、
[N[0]]
に設定します。
= [114] 繰り返し、
== [115] 
[VAR[レコード]]を、受信した[[バイト列]]に設定します。
[VAR[レコード]]の[[長さ][バイト長]]が[VAR[復号器]]の[F[レコードサイズ]]に等しくなるか、
受信[[バイト列]]の末尾に到達するまでとします。
== [120] 
[VAR[詰め区切子オクテット]]を、
[VAR[レコード]]の末尾から順に [N[0x00]] でない[[バイト]]を探した結果に設定します。
== [121] 
[VAR[詰め区切子オクテット]]が [CODE[null]] の場合、
=== [122] [MUST[失敗]]を返し、ここで停止します。
[SRC[>>6 2.]]
== [33] 
[VAR[詰め区切子オクテット]]が [N[0x01]] でも [N[0x02]] でもない場合、
=== [34] [MUST[失敗]]を返し、ここで停止します。
[SRC[>>6 2.]]
== [123] 
[VAR[詰め区切子オクテット]]が [N[0x01]] で[VAR[レコード]]の[[長さ][バイト長]]が[VAR[復号器]]の[F[レコードサイズ]]で''ない''場合、
=== 
;; [35] この条件は[[仕様書]]に明記されていませんが、
最終レコードが [N[0x02]] でなければ[MUST[失敗]]という要件が適用されます。
=== [124] [MUST[失敗]]を返し、ここで停止します。
== [179] [VAR[単一レコード]]が[[真]]で、
[VAR[詰め区切子オクテット]]が [N[0x01]] の場合、
=== [180] [MUST[失敗]]を返し、ここで停止します。
[SRC[>>137 4.]]
== [125] 
[VAR[暗号文]]を、
[VAR[レコード]]から末尾の [N[0x00]] [[バイト]]とその直前の[VAR[詰め区切子オクテット]]を除去した結果に設定します。
[SRC[>>6 2.]]
== [116] 
[VAR[nonce]]
を、
[VAR[復号器]]の [F[PRK]] 
と[VAR[復号器]]の [F[SEQ]]
について
[[nonce]] を取得した結果に設定します。
== [94] 
[VAR[平文]]を、
[CODE[AEAD_AES_128_GCM]]
の[[解読]]を実行した結果に設定します。
[FIG(list members)[
: [VAR[鍵]] : [VAR[復号器]]の [F[CEK]]
: [VAR[nonce]] : [VAR[nonce]]
: [VAR[暗号文]] : [VAR[暗号文]]
: [VAR[関連付けされたデータ]] : [[空バイト列]]
[NOTE[
[40] [[RFC 8188]] では「additional data」と呼ばれています。
]NOTE]
]FIG]
== [212] [VAR[平文]]が[[失敗]]の場合、
=== [214] [[失敗]]を返し、ここで停止します。
[SRC[仕様書になし]]
== [29] [[Assert]]:
[VAR[暗号文]]の[[長さ][byte length]] = [VAR[平文]]の[[長さ][byte length]] + [N[16]]
== [42] [VAR[結果バイト列]]に、[VAR[平文]]を[[末尾に追加]]します。
== [39] [VAR[復号器]]の [F[SEQ]] を、 [N[1]] [[インクリメント]]します。
== [37] [VAR[詰め区切子オクテット]]が [N[0x02]] の場合、
=== [126] 次のバイトを受信するか、受信[[バイト列]]の終端に到達するのを待ちます。
=== [127] バイトを受信した場合、
==== 
;; [41] 最終レコードを受信しましたが、まだ次のレコードがあります。
==== [128] [MUST[失敗]]を返し、ここで停止します。
[SRC[>>6 2.]]
=== [38] ここで停止します。
= [117]
;; [129] 最終レコードを受信していませんが、もう次のレコードがありません。
= [119] [MUST[失敗]]を返し、ここで停止します。
[SRC[>>6 2.]]


]FIG]

[131] 
[VAR[結果バイト列]]が[[復号]]した結果になります。

[118] 
[[仕様書]]はエラー処理をほとんど曖昧に濁しています。
[[レコード]]を[[失敗]]とみなすべき条件がいくつか定められていますが、
そのときまでに得られた[VAR[結果バイト列]]をそのまま使って良いのかどうか、
[[失敗]]した[[レコード]]から得られた情報を[VAR[結果バイト列]]に含めて良いのか、
[[仕様書]]からは読み取れません。

[132] 特に問題なのは、[VAR[詰め区切子オクテット]]が最終レコードなら [N[0x02]]、
それ以外なら [N[0x01]] でなければ[RUBYB[[[失敗]]][fail]]としなければ[MUST[ならない]]
[SRC[>>6 2.]] との条件です。[[ストリーミング処理]]でこの要件を満たすためには、
次のレコード (または入力の末端) まで処理を進める必要があります。
[[仕様書]]がどうとでも解釈できるので、
実装によって、あるいは[[ネットワーク]]からの[[バイト]]の到着タイミング次第で、
挙動が変わってくるおそれがあります。

[62] 
途中で途切れたメッセージでも本手法は処理できますが、
完全なメッセージであるものとして処理しては[MUST[なりません]]。
途中までのメッセージでも処理する受信者は、
攻撃者によりメッセージが途中で切られた可能性も考慮する必要があります。
[SRC[>>6 4.2.]]
この規定の存在が、[[失敗]]の扱いの解釈を難しくします。
途切れた[[メッセージ]]や[[ランダムアクセス]]の処理では、
入力中最後の[[レコード]]の[VAR[詰め区切子オクテット]]が [N[0x02]]
でなくても認めなければなりません。
このような実装方針次第でいかようにもなりそうな曖昧な規定方法は、
[[セキュリティー]]問題の温床でしかありません。

[157] 途中の[[レコード]]で解読に失敗した場合、
無視して次の[[レコード]]に進むべきなのか、停止するべきなのか不明です。

-*-*-

[61] 
本手法を使って内容の起源を認証する[[受信者]]は、
[CODE[aes128gcm]]
[[内容符号化]]を含まない[[HTTPメッセージ]]を拒絶しなければ[MUST[なりません]]。
[[内容符号化]]が自動的に除去されて、最終的な受信者がそれに気づかないおそれがあります。
[SRC[>>6 4.1.]]

-*-*-

[174] 
[[Web Push]]
[[利用者エージェント]]は、
複数の[[レコード]]に対応する必要はありません。
[[利用者エージェント]]は
[F[[CODE[rs]]]]
を無視して[MAY[構いません]]。
([[レコードサイズ]]を検査しなくても、
妥当な場合に[[解読]]は高い確率で失敗します。)
しかし[[詰め区切子オクテット]]は検査しなければ[MUST[なりません]]。
[N[0x02]] 以外の[[詰め区切子オクテット]]がある場合、
[[メッセージ]]を捨てなければ[MUST[なりません]]。
[SRC[>>137 4.]]
つまり[VAR[単一レコード]]を[[真]]として実行しなければなりません。

[177] 
とひどい規定があります。そんなことが許されるなら、
実際の[[レコードサイズ]]と矛盾する
[F[[CODE[rs]]]]
が指定されても受け入れられる可能性と拒絶される可能性があるわけで、
[[相互運用性]]のリスクでしかありません。

[178] 
しかも、複数[[レコード]]に対応する必要がないということは、
対応してもいいように聞こえますが、
[N[0x02]] かどうか検査しなければならないということは、
複数[[レコード]]に対応してはいけないということです。
このような制限が認められることが
[CODE[aes128gcm]]
の仕様書に一言も触れられていないのも問題です。

[176] 
[[Web Push]]
では
[CODE[aes128gcm]]
をちょうど1回だけ適用することが求められていますが、
受信者がどうするべきか定められていません。
[[内容符号化]]が使われていない場合や、
他の[[内容符号化]]が使われている場合にどう処理するべきかは不明です。

[183] [[Web Push]] 専用に簡略化した[[復号]]は、
次のようにします。

[FIG(steps)[
= [184] [VAR[結果バイト列]]を、[[空バイト列]]に設定します。
= [185] [[高々]]4096バイト受信します。
= [186] 次に受信したのが受信[[バイト列]]の末尾でない場合、
== [187] [[失敗]]を返し、ここで停止します。
= [188] [VAR[受信バイト列]]を、受信[[バイト列]]に設定します。
= [189] [VAR[受信バイト列]]の[[長さ][バイト長]]が [N[86]]
[[未満]]の場合、
== [190] [[失敗]]を返し、ここで停止します。
= [191] [VAR[salt]]
を、[VAR[受信バイト列]]の先頭16バイトに設定します。
= [192] [VAR[レコードサイズ]]を、
[VAR[受信バイト列]]の第17バイトから第20バイトを[[ネットワークバイト順]]の[[32ビット符号無し整数]]として解釈した結果に設定します。
= [193] [VAR[レコードサイズ]]が
[N[18]]
[[未満]]または[VAR[受信バイト列]]の[[バイト長]] - 86
[[未満]]の場合、
== [194] [[失敗]]を返し、ここで停止します。
= [195] [VAR[識別子長]]を、
[VAR[受信バイト列]]の第21バイトを[[8ビット符号無し整数]]として解釈した結果に設定します。
= [196] [VAR[識別子長]]が [N[65]] でない場合、
== [197] [[失敗]]を返し、ここで停止します。
= [198] [VAR[鍵識別子]]を、
[VAR[受信バイト列]]の第22バイトから第86バイトの[[バイト列]]に設定します。
= [199] [VAR[IKM]] を、
[VAR[鍵識別子]]について受信時の
[[IKM]]
を取得した結果に設定します。
= [217] [VAR[IKM]] が[[失敗]]の場合、
== [218] [[失敗]]を返し、ここで停止します。
= [200] [VAR[PRK]] を、
[VAR[salt]]、
[VAR[IKM]] について
[[PRK]] を取得した結果に設定します。
= [201] [VAR[CEK]] を、
[VAR[PRK]]
について
[[CEK]] を取得した結果に設定します。
= [202] 
[VAR[受信バイト列]]のうち、
第1バイトから第86バイトを除去します。
= [203] 
[VAR[受信バイト列]]から、
末尾の
[N[0x00]]
をすべて除去します。
= [204] 
[VAR[受信バイト列]]が[[空バイト列]]の場合、
== [205] [[失敗]]を返し、ここで停止します。
= [206] 
[VAR[受信バイト列]]の最後のバイトが [N[0x02]] で''ない''場合、
== [207] [[失敗]]を返し、ここで停止します。
= [208] 
[VAR[受信バイト列]]から最後のバイトを除去します。
= [209] 
[VAR[nonce]]
を、
[VAR[PRK]] 
と
[N[0]]
について
[[nonce]] を取得した結果に設定します。
= [210] 
[VAR[平文]]を、
[CODE[AEAD_AES_128_GCM]]
の[[解読]]を実行した結果に設定します。
[FIG(list members)[
: [VAR[鍵]] : [VAR[CEK]]
: [VAR[nonce]] : [VAR[nonce]]
: [VAR[暗号文]] : [VAR[受信バイト列]]
: [VAR[関連付けされたデータ]] : [[空バイト列]]
]FIG]
= [211] [[Assert]]: [[平文]]は[[バイト列]]または[[失敗]]。
= [213] [VAR[平文]]を返します。
]FIG]

;; [226] こうしてみると、かなり簡略化できたものの実質的な意味のない検査がたくさん残ってしまって、
汎用的な複数レコード対応した手法を1レコードで使ったことの無駄さが感じられます。


* 歴史

[1] [CITE@en[Encrypted Content-Encoding for HTTP]]
([TIME[2016-04-06 02:26:28 +09:00]] 版)
<http://httpwg.org/http-extensions/draft-ietf-httpbis-encryption-encoding.html>

[2] [CITE@en[draft-ietf-webpush-encryption-08 - Message Encryption for Web Push]]
([TIME[2017-04-16 18:28:05 +09:00]])
<https://tools.ietf.org/html/draft-ietf-webpush-encryption-08>

[3] [[IANA登録簿]]に [CODE[aes128gcm]] が登録されました。 [TIME[2017-04-21T03:50:50.600Z]]

[4] [CITE@en[Add PushManager.supportedContentEncodings (#252)]]
([[beverloo]]著, [TIME[2017-04-26 01:13:27 +09:00]])
<https://github.com/w3c/push-api/commit/813f9af75d59e3fa1522db9aeeaa2bd158ff10bf>

[5] [CITE@en[encrypted-content-encoding/ece.js at master · web-push-libs/encrypted-content-encoding]]
([TIME[2019-01-04 13:12:20 +09:00]])
<https://github.com/web-push-libs/encrypted-content-encoding/blob/master/nodejs/ece.js>

[134] 古い案では[[内容符号化]]
[DFN[[CODE[aesgcm]]]],
[[内容符号化]] [DFN[[CODE[aesgcm128]]]],
[[HTTPヘッダー]] [DFN[[CODE(HTTP)@en[Encryption:]]]],
[[HTTPヘッダー]] [DFN[[CODE(HTTP)@en[Encryption-Key:]]]],
[[HTTPヘッダー]] [DFN[[CODE(HTTP)@en[Crypto-Key:]]]]
が提案されていました。

[8] 
[TIME[平成29(2017)年6月][2017-06]]、
[CODE[aes128gcm]]
の[[仕様書]]が
[[IETF]]
の[[提案標準]]
[DFN[RFC 8188]]
が出版されました
[SRC[>>6]]。

[133] それにしてもこの時代に出版されたとは思えない、古き悪しき時代を思わせる曖昧な[[仕様書]]です。
構文、生成側処理、解釈側処理のどれなのかはっきりしない曖昧な規定、
[[事実の文]]ベースで実装の要件がはっきりしない上に、
用語の表記がその場その場で少しずつ揺れていて意図的かどうかわかりにくい、
挙動が用語のイメージの暗黙の了解によって規定され明文化されていない、
といった [[IETF]]
にありがちな空気を読まないといけない[[仕様書]]。
こんなもので[[相互運用性]]は維持できるのでしょうか。

[139] 
[TIME[平成20(2017)年11月][2017-11]]、
[[Web Push]] での[[暗号化]]方式の[[仕様書]]が
[[IETF]] 
の[[提案標準]]
[DFN[RFC 8291]]
として出版されました
[SRC[>>137]]。


