[32] [[VAPID]] は[[HTTP認証]]の[[認証方式]]の1つです。
[[Web Push]] で使われます。

* 仕様書

[REFS[
- [1] [CITE@en[RFC 8292 - Voluntary Application Server Identification (VAPID) for Web Push]], [TIME[2020-03-09 00:13:41 +09:00]] <https://tools.ietf.org/html/rfc8292>
- [63] [CITE@en[RFC 8292 - Voluntary Application Server Identification (VAPID) for Web Push]], [TIME[2020-03-09 00:13:41 +09:00]] <https://tools.ietf.org/html/rfc8292#section-4.2>
- [35] [CITE@en-US[Push API]], [TIME[2020-02-04 16:21:34 +09:00]] <https://w3c.github.io/push-api/#dom-pushmanager-subscribe>
- [259] [CITE@en-US[Push API]], [TIME[2020-02-04 16:21:34 +09:00]] <https://w3c.github.io/push-api/#dom-pushsubscriptionoptions-applicationserverkey>
- [46] [CITE@en-US[Push API]], [TIME[2020-02-04 16:21:34 +09:00]] <https://w3c.github.io/push-api/#dom-pushsubscriptionoptionsinit-applicationserverkey>
]REFS]

* プロトコル

[24] [[auth-scheme]] として [CODE[vapid]]、
[[MIME型]]として [CODE[application/webpush-options+json]]
が定義されています。


* 鍵と JWT データ

[16] 
[[アプリケーションサーバー]]は、
[[VAPID]]
[[署名]]用の[[鍵ペア]]を生成しておきます。
これは 
[[P-256 curve]]
の
[[ECDSA]]
で使えるものでなければ[MUST[なりません]]。
この[[鍵]]によって、
[[プッシュサービス]]は複数の[[プッシュメッセージ]]の[[送信][プッシュ資源]]に渡って[[アプリケーションサーバー]]を識別できるのであります。
[SRC[>>1 2.]]

;;
[22] 
[[プッシュサービス]]は
[[DoS攻撃]]のリスクに晒されており、
[[プッシュメッセージ]]を送信してくる[[アプリケーションサーバー]]を識別し制限することができるようになります。
[[プッシュメッセージ]]を捨てるかどうかの判断で、
行儀の良い[[アプリケーションサーバー]]を識別できない[[アプリケーションサーバー]]より優遇したりできます。
[SRC[>>1 1.1.]]

;; [23] 
また、[[プッシュ資源]]を、 [[URL]] が秘匿されることだけに頼らず、
より確実に[[アプリケーションサーバー]]だけが[[プッシュメッセージ]]を送信できるようになります。
[SRC[>>1 1., 4.]]

[50] [[アプリケーションサーバー]]は、事前に
[[VAPID]]
用[[鍵ペア]]を1組用意しておいて、
これを[[プッシュ購読]]の作成のときに毎回使うことが想定されています。
必要であれば、同じ[[アプリケーションサーバー]]で複数の[[鍵ペア]]を使うこともできますが、
ある1つの[[プッシュ購読]]で使う[[鍵ペア]]は、特定の1組である必要があります。

[14] 
[[アプリケーションサーバー]]は、
[[プッシュメッセージ]]のデータの[[暗号化]]に使う
[CODE[aes128gcm]]
の[[鍵]]と、
[[VAPID]]
で使う[[鍵]]に違ったものを使わなければ[MUST[なりません]]。
[SRC[>>1 3., >>259]]
[CODE[aes128gcm]]
の[[鍵]]はその場その場の使い捨て、
[[VAPID]] 
の[[鍵]]は恒久利用、
と性質も違います。

-*-*-

[17] 
[[アプリケーションサーバー]]は、
[[プッシュメッセージ]]の[[送信][プッシュ資源]]にあたって、
次のような [[claim]] を持つ [[JWT]] を作成して[[署名]]します。
[SRC[>>1 2.]]

[FIG(list members)[

: [CODE[aud]] ([RUBYB[聴衆][audience]]) :
[[プッシュ資源]]の [[URLの起源]]の
[[Unicode直列化]]でなければ[MUST[なりません]]。
この [[JWT]] は当該[[プッシュサービス]]宛となるのと同時に、
[[同じ起源]]の[[プッシュ資源]] [[URL]] すべてに対して再利用可能となります。
[SRC[>>1 2.]]
[[廃止]]され他の場面では使われなくなった
[[Unicode直列化]]がここで使われることが気にかかりますが、
[[プッシュサービス]]の [[URL]] に敢えて [[IDN]]
を使う必然性は無さそうなので、
[[ASCII直列化]]でも問題になることはほとんど無いでしょうか。
(しかし不安になりますね。)
値は [[JWT]] [CODE[StringOrURI]] です。
つまり [CODE[:]] を含むときは [[RFC 3986]] [[URI]]
とされています。ということは実は [[Unicode直列化]]は
[[JWT]] の構文制約違反です。
: [CODE[exp]] ([RUBYB[有効期限][expiry]]) :
それ以後本 [[JWT]] が[[満期]]する[[時刻]]を指定しなければ[MUST[なりません]]。
[[要求]]時点から24時間を超えては[MUST[なりません]]。
[SRC[>>1 2.]]
値は [[JWT]] [CODE[NumericDate]] です。
: [CODE[sub]] (主題) :
連絡先詳細を示したい場合、指定して[MAY[構いません]]。
[CODE[mailto:]] [[URL]]
か
[CODE[https:]] [[URL]]
を含む[SHOULD[べきです]]。
[SRC[>>1 2.1.]]
値は [[JWT]] [CODE[StringOrURI]] です。
[[プッシュサービス]]運営者は、
異常に多くのデータを送ってくる[[アプリケーションサーバー]]の運営者に連絡したいなど、
理由あって連絡先を知りたいことがあります。
[SRC[>>1 1.1.]]
一方容易に詐称可能なことに要注意です [SRC[>>1 5.]]。
[NOTE[
[29] 
類似例として
[CODE[From:]] [[HTTPヘッダー]]や
[CODE[User-Agent:]] [[HTTPヘッダー]]に連絡先を記述する慣習が提唱されたり、
一部で行われていたりしましたが、
成功したとは言い難いです。
[CODE[sub]]
も果たして有効に利用されているのでしょうか。
]NOTE]
: その他 :
その他任意の [[public claim name]], [[private claim name]]
の [[claim]] を使うことができます。
しかし [[JWT]] のサイズは可能な限り小さく抑える[SHOULD[べきです]]。
[SRC[>>1 2.2.]]

]FIG]

[20] 
[[JWT]] は [[JWS]] を使わなければ[MUST[なりません]]。
[[署名]]は 
[CODE[ES256]] 
を使わなければ[MUST[なりません]]。
[SRC[>>1 2.]]

-*-*-

[26] 
[[アプリケーション]]は、 [[Push API]]
で[[利用者エージェント]]に[[公開鍵]]を引き渡します。

[45] 
指定する場合、
[[P-256 curve]]
上の[[点]]を
[[X9.62 uncompressed form]]
で符号化したものでなければ[MUST[なりません]]。
[CODE[DOMString]]
の場合、それを [[RFC 7515]] [[base64url]]
符号化したものでなければ[MUST[なりません]]。
[SRC[>>259]]

[47] 
[CODE[PushSubscriptionOptionsInit]]
[[辞書]]の
[DFN[[CODE[applicationServerKey]]]]
[[メンバー]]は、
[[プッシュ購読]]作成時に使う[[公開鍵]]を指定するものです。
[CODE[BufferSource]]
または
[CODE[DOMString]]
または
[CODE[null]]
で、[[既定値]]が
[CODE[null]]
です。 [SRC[>>46]]


[33] 
[CODE[PushSubscriptionOptions]]
[[インターフェイス]]の
[DFN[[CODE[applicationServerKey]]]]
[[属性]]の[[取得器]] [SRC[>>259]] は、
次のようにしなければ[MUST[なりません]]。

[FIG(steps)[
= [34] 
[[文脈オブジェクト]]の
[F[[CODE[applicationServerKey]]]]
を返します [SRC[>>259]]。
[CODE[ArrayBuffer]] または [CODE[null]] です [SRC[>>259]]。
]FIG]

[36] 
[CODE[subscribe]]
[[メソッド]]における[VAR[オプション群]]について
[F[[CODE[applicationServerKey]]]]
の正規化は、次のようにします。
[SRC[>>35]]

[FIG(steps)[
= [37] 
[VAR[オプション群]]の
[F[[CODE[applicationServerKey]]]]
が
[CODE[null]]
で''ない''場合、
== [38] 
[VAR[オプション群]]の
[F[[CODE[applicationServerKey]]]]
が
[CODE[DOMString]] の場合、
=== [39] [VAR[バイト列]]を、
[VAR[オプション群]]の
[F[[CODE[applicationServerKey]]]]
を
[[RFC 7515]]
[[base64url]]
復号した結果に設定します。
=== [40] 
[VAR[バイト列]]が[[失敗]]の場合、
==== [41] 
[CODE[InvalidCharacterError]]
[CODE[DOMException]]
を[[投げ]]、
ここで停止します。
=== [42] 
[VAR[オプション群]]の
[F[[CODE[applicationServerKey]]]]
を、
[VAR[バイト列]]の
[CODE[ArrayBuffer]]
に設定します。
== [43] 
[VAR[オプション群]]の
[F[[CODE[applicationServerKey]]]]
が
[[P-256 curve]]
上の妥当な点を表さ''ない''場合、
=== [44] 
[CODE[InvalidAccessError]]
[CODE[DOMException]]
を[[投げ]]、
ここで停止します。
]FIG]


[25] 
[[利用者エージェント]]は、
[[プッシュサービス資源]]の[[要求]]で、[[公開鍵]]を送信します。
[[プッシュサービス]]は、作成した[[プッシュメッセージ購読]]でこれを保持します。

[27] 
[[アプリケーションサーバー]]は、
[[プッシュ資源]]の[[要求]]で 
[[auth-scheme]]
[CODE[vapid]]
を使います。
[[プッシュサービス]]は[[プッシュメッセージ購読]]の保持する[[公開鍵]]を使ってこれを検証します。


* auth-scheme

[2] [[HTTP]] [[auth-scheme]]
[DFN[[CODE[vapid]]]]
は、[[署名]]した [[JWT]]
と、その[[署名]]の[[鍵]]を示すものです。
[SRC[>>1 3.]]

[9] [[Web Push protocol]] 
で使うことを意図しています。
[SRC[>>1 3.]]

[4] [[起源サーバー]]の認証に使えます。
[[プロキシ認証]]
([CODE[Proxy-Authenticate]], [CODE[Proxy-Authorization]])
で使っては[MUST[なりません]]。
[SRC[>>1 3.]]

[5] 
[[challenge]]
([[応答]])
は、
[CODE[auth-scheme]] である [CODE[vapid]]
のみ含みます。[[引数][auth-param]]はありません。
[SRC[>>1 3.]]

[6] 
[[credentials]]
([[要求]])
は、
[CODE[auth-scheme]] である [CODE[vapid]]
に[[引数][auth-param]]として 
[CODE[t]]
と 
[CODE[k]]
を指定します。
[SRC[>>1 3.]]

-*-*-

[12] 
[DFN[[CODE[k]]]]
[[引数][auth-param]]は、
[[公開鍵]]を指定するものです。
[[プッシュサービス]]が [[JWT]] を検証できるようにするものです。
[[ECDSA]] [[公開鍵]]を、
[[X9.62]] [[uncompressed form]]
で、
[[RFC 7515]]
[[base64url]]
符号化したものを指定します。
[SRC[>>1 3.]]

;; [13] [[JWT]] を使っているのに [[JWK]] でなく [[uncompressed form]]
なのは、
[[JWK]] には[[正準形]]がないことと、
サイズが小さくなるためとされています。
[SRC[>>1 3.]]


[15] 
[[プッシュサービス]]は、
[CODE[aes128gcm]]
の[[公開鍵]] ([CODE[keyid][aes128gcm]])
と
[[VAPID]]
の[[公開鍵]] ([CODE[k][vapid]])
が同一の値なら、
[CODE[400]]
[[応答]]を返す[SHOULD[べきです]]。
[SRC[>>1 3.]]

-*-*-

[11] 
[DFN[[CODE[t]]]]
[[引数][auth-param]]は、
[[JWT]] を指定するものです。
[SRC[>>1 3.]]

[18] 
[[プッシュサービス]]は、
[[JWT]] の[[署名]]か [[claim]]
が[[非妥当]]なら、
[CODE[403]]
[[応答]]を返して[MAY[構いません]]。
[[プッシュサービス]]は[[非妥当]]な [[JWT]]
の情報を使っては[MUST[なりません]]。
[SRC[>>1 2.]]

;; [19] 検証したりしなかったりして構わないということで、
[[相互運用性]]の問題になりそうですが。

-*-*-

[8] 
[CODE[realm]]
[[引数][auth-param]]は無視します。
[SRC[>>1 3.]]

[10] 指定することが認められているのか明記されていません。

[7] 未知・未対応の[[引数][auth-param]]は、
無視しなければ[MUST[なりません]]。
[SRC[>>1 3.]]

[21] 
使用する [[JWT]] や[[署名]]の方法は固定されており、
他の署名方法などが必要になったときは別の
[[auth-scheme]]
が必要となります。
[[HTTP認証]]の [[auth-scheme]]
の[[折衝]]の仕組みを流用するためとされています。
[SRC[>>1 2.3.]]

-*-*-

[49] 
[[アプリケーションサーバー]]は、[[プッシュメッセージの送信]]時、
[VAR[[[プッシュ購読]]]]の[F[特定アプリケーションサーバーに制限されている][applicationServerKey]]が[[真]]の場合、
[[アプリケーションサーバー]]の 
([VAR[[[プッシュ購読]]]]用の)
[[VAPID]] 用[[鍵ペア]]の[[秘密鍵]]で[[署名]]した [[JWT]]
を含めなければ[MUST[なりません]]。
[SRC[>>1 4.2]]
これは
[[auth-scheme]] [CODE[vapid]]
の 
[[credentials]] を
[CODE[Authorization:]]
[[ヘッダー]]で指定することによります。
[SEE[ [[プッシュメッセージの送信]] ]]

-*-*-

[48] 
妥当な [CODE[vapid]] の [[credentials]]
を含まないなら拒絶しなければ[MUST[なりません]]。
[[認証]]がないとき
[CODE[401]]
[[応答]]を返せますし、
[[認証]]が非妥当なとき
[CODE[403]]
[[応答]]を返せます。
非妥当には次のような場合があります。
[SRC[>>63]]

- [65] [[JWT]] か[[公開鍵]]が含まれない時。
- [66] [[JWT]] の[[署名]]の[[検証]]に成功しない時
- [67] [[現在時刻]]が [[JWT]] の [CODE[exp]] を過ぎている時
- [68] [[現在時刻]]が [[JWT]] の [CODE[exp]] まで24時間を超える時
- [69] [[プッシュ資源]]の[[URLの起源]]が [[JWT]] の [CODE[aud]]
でない時
- [70] [[JWT]] の[[署名]]の[[公開鍵]]が[[プッシュメッセージ購読]]作成時に指定された[[公開鍵]]でない時


-*-*-

[28] [[VAPID]] は[[再生攻撃]]に弱いです。
[[HTTPS]] を使っていれば [[JWT]] は漏れませんし、
[CODE[exp]] を短くすれば漏洩時の悪用のリスクは抑えられます。
[SRC[>>1 5.]]

[30] 一方で[[署名]]計算は少なからず資源を消費するので、
[[DoS攻撃]]中に正当な[[要求]]を識別するための [[VAPID]]
としては矛盾した状況に置かれています。
そこで[[アプリケーションサーバー]]は [[JWT]]
を再利用することが[RUBYB[推奨][encourage]]されており、
[[プッシュサービス]]は検証結果を[[キャッシュ]]できます。
[SRC[>>1 5.]]

* 歴史

[3] 
[TIME[2017年11月][2017-11]]に [[IETF]]
の[[提案標準]]
[DFN[RFC 8292]]
として出版されました。
[SRC[>>1]]

[31] [CITE[Web Push Parameters]], [TIME[2017-12-27 01:57:03 +09:00]] <https://www.iana.org/assignments/webpush-parameters/webpush-parameters.xhtml>

* メモ