1. 目的
プライベートステートトークンの目的は、プライバシーを保護しながら、サイト間で限定的なシグナルを時間を超えて伝達することです。これは、IETF Privacy Pass作業部会の作業文書で規定されるprivacy passプロトコルのバリアント[PRIVACY-PASS-ISSUANCE-PROTOCOL]、[PRIVACY-PASS-WG]を利用することで実現されます。プライベートステートトークンは、Privacy PassのバリアントのWebプラットフォーム実装と見なせます。
この仕様では、リクエスト辞書に新しいフィールドを導入し、トークン操作をサポートします。この新しい辞書を通してプライベートステートトークンの利用方法を記述します。
2. 背景
プライベートステートトークンAPIは、匿名認証の仕組みを提供します。ユーザーエージェントが提供するAPI自身はクライアントの認証を行わず、認証情報の伝達を補助します。
クライアントの認証とトークン署名は、発行者と呼ばれる同じ主体によって行われます。これは[PRIVACY-PASS-ARCHITECTURE]、[PRIVACY-PASS-AUTH-SCHEME]で説明されている共同証明・発行者アーキテクチャです。
ユーザーエージェントはトークンを永続的ストレージに保存します。ナビゲーションしたオリジンは、ファーストパーティコンテキストでトークンを取得・消費(spend)できるほか、トークンの取得・消費を行うサードパーティコードを含むこともあります。トークンの消費はリディーム(引換)と呼ばれます。
オリジンはユーザーエージェントに対し、任意の発行者からのトークン取得を要求できます。トークンは取得元とは異なるオリジンで引換できます。
プライベートステートトークンAPIは、リンク可能な状態を持つCookie[RFC6265]を用いることなく、クロスサイト匿名認証を実現します。Cookieもクロスサイト認証を提供しますが、匿名性を提供できません。
Cookieは大量の情報を保存します。[RFC6265]では、少なくとも1Cookieあたり4096バイト、ドメインあたり50Cookieが必要です。つまりオリジンは 50 x 4096 x 2^8個のユニークIDを持てます。バックエンドDBがあれば、その分のユーザー・セッションに任意のデータを保存可能です。
Cookieと比べ、プライベートステートトークンに保存できるデータ量は非常に限られます。トークンは6値の集合から1つの値(例えば6通りの値が取れるenum型)を保持します。つまりトークンが持つ情報量は2-3ビット(4 < 6 < 8)で、4096バイト保存できるCookieと比べ非常に小さい値です。
さらに、プライベートステートトークンAPIは暗号プロトコルを用い、発行時にオリジンがどのトークンをどのユーザーに発行したか追跡できないようにします。発行者は自分が発行したトークンかどうか検証できますが、発行時の文脈に紐づけることはできません。Cookieはこの特性を持ちません。
Cookieとは異なり、発行者から複数のトークンを保存しても、トークン同士がリンク不可能なためユーザーのプライバシーは損なわれません。プライベートステートトークンAPIでは、トップレベルオリジンごとに最大2つまで異なる発行者を許可しています。これは発行者が協調する場合にも、ユーザーごとに格納できる情報量を制限するためです。
プライベートステートトークンの操作は[FETCH]に依存します。特定のプライベートステートトークン操作に対応するfetchリクエストを作成し、fetch関数のパラメータとして利用できます。
3. 発行者公開鍵
この節では、プライベートステートトークンプロトコルで利用する公開鍵を発行者が提供するために必要な公開インターフェースについて説明します。
発行者は鍵の集合を保持し、発行(Issue)およびリディーム(Redeem)暗号関数を実装し、トークン署名および検証を行う必要があります。発行者は鍵コミットメントエンドポイントを提供する必要があります。鍵コミットメントは、発行・引換操作のために必要な暗号鍵と関連メタデータの集合です。発行者はこれらを安全なHTTP[RFC8446]エンドポイント経由で公開します。ユーザーエージェントは鍵コミットメントを定期的に取得すべきです。鍵コミットメントは、発行・引換操作のために必要な暗号鍵と関連メタデータの集合を表すマップです。
鍵コミットメントエンドポイントへのリクエストは、次のような形式のJSONレスポンス[RFC8259]を返す必要があり、そのメディアタイプは"application/pst-issuer-directory"です:
{ < cryptographic protocol_version>: { "protocol_version" : < cryptographic protocol version> , "id" : < key commitment identifier> "batchsize" : < batch size> , "keys" : { < keyID>: { "Y" : < base64- encodedpublic key> , "expiry" : < key expiration date> }, < keyID>: { "Y" : < base64- encodedpublic key> , "expiry" : < key expiration date> }, ... } }, ... }
-
<cryptographic protocol version>は、利用するプライベートステートトークンプロトコルバージョンを表す文字列識別子です。同じ文字列が内側の"protocol_version"フィールドにも使われます。プロトコルバージョンの文字列識別子は"PrivateStateTokenV1VOPRF"です。-
プロトコルバージョン
“PrivateStateTokenV1VOPRF”は[VOPRF]暗号プロトコルを実装します。発行者は有効なトークン署名鍵を最大6個まで使えます。
-
-
"id"フィールドは鍵コミットメントの識別子を提供します。これは非負の整数で、符号なし32ビット整数型の範囲内です。値は単調増加するべきです。 -
"batchsize"は、各トークン発行操作で発行者がサポートするマスク済トークンの最大数を指定します。値は正の整数です。ユーザーエージェントは実際はそれより少ないトークンを送る場合もありますが、一般的にはbatchsize個のトークンを1操作で送信するのがデフォルトです。 -
"keys"フィールドは、公開鍵の辞書であり、識別子毎に列挙します。
全てのフィールド名およびその値は文字列です。発行者の新しい鍵コミットメントが取得されると、以前のコミットメントは破棄されます。
3.1. 発行者鍵の取得・登録
本APIのプライバシーを維持しユーザー固有鍵を避けるため、発行者はトークンの発行・引換に対して全クライアントに同じ鍵を提示するべきです。
この性質を実現するため、ユーザーエージェントはある種のプロキシメカニズムや集中型の鍵取得・配信機構を通じて、ユーザー非依存の方法で鍵コミットメントを取得することが推奨されます。
集中型の鍵取得機構を使う場合、ユーザーエージェントが発行者を登録し、鍵コミットメントを定期的に取得・各クライアントへ配信する登録手続きを設ける必要があります。登録の要件や手法は実装依存です。
登録処理を用いる場合、ユーザーエージェントは登録要求に有効期限を付与し、廃止または非アクティブな発行者を削除できるようにすることが推奨されます。
4. VOPRFメソッド
この文書では、[RFC8446]のセクション3で定義されたTLS表現言語でプロトコルメッセージをエンコードします。
プロトコルメッセージの直列化およびプロトコルメッセージの逆直列化は、[RFC8446]のセクション3に従ってエンコード、および解釈されます。
プライベートステートトークンでは、VOPRFプロトコルを初期化する際、カーブにはP-384(以下の関数内のG)を使い、nonce_sizeは64と定義されています。
注: 現バージョンのPSTでは入力・出力にX9.62アンコンプレストポイントを使うのは既存[VOPRF]仕様と歴史的に異なります。またnonce_sizeの選定も[BatchedTokens]の現ドラフトと異なります。
サーバーが発行処理を行う際は、BlindEvaluateBatch関数([BatchedTokens]プロトコル)の実行となり、入力のIssueRequest・出力のIssueResponseは以下のように直列化されます:
// Scalarsは楕円曲線スカラー値(P-384なら48バイト)。
// ECPointsはX9.62アンコンプレストポイント(P-384なら97バイト)でエンコードされた楕円曲線ポイントです。
struct {
uint16 count;
ECPoint nonces[count]; // blindedElementsに対応
} IssueRequest;
struct {
ECPoint evaluated; // evaluatedElementsに対応
} SignedNonce;
struct {
Scalar c;
Scalar s;
} DLEQProof;
struct {
uint16 issued;
uint32 key_id;
SignedNonce signed[issued];
opaque proof<1..2^16-1>; // DLEQProof構造体の直列化バイト列
} IssueResponse;
サーバーがリディームを実行する際は、PSTEvaluateをTokenに対して実行し、入力のRedeemRequest・出力のRedeemResponseは次のように直列化されます:
struct {
uint32 key_id;
opaque nonce[nonce_size];
ECPoint W;
} Token;
struct {
opaque token<1..2^16-1>; // Token構造体の直列化バイト列
opaque client_data<1..2^16-1>;
} RedeemRequest;
struct {
opaque rr<1..2^16-1>;
} RedeemResponse;
プライベートステートトークンは、PSTFinalizeを定義しており、これはFinalizeBatch([BatchedTokens]プロトコル)のバリアントです:
def PSTFinalize(input, blinds, evaluatedElements,
blindedElements, pkS, proof):
if VerifyProof(G.Generator(), pkS, blindedElements,
evaluatedElements, proof) == false:
raise VerifyError
// PST: バッチ処理構造を利用。
unblindedElements = []
for index in range(evaluatedElements.length):
N = G.ScalarInverse(blinds[index]) * evaluatedElements[index]
unblindedElements.append(G.SerializeElement(N))
// PST: ハッシュ出力ではなくunblindedElementsを返す。
return unblindedElements
プライベートステートトークンは、PSTEvaluateを定義しており、これはEvaluate([VOPRF]プロトコル)のバリアントです:
// skSはサーバーがkey_idで対応する秘密鍵を取得して決定。
def PSTEvaluate(skS, nonce, W, client_data):
inputElement = G.HashToGroup(nonce)
if inputElement == G.Identity():
raise InvalidInputError
evaluatedElement = skS * inputElement
issuedElement = G.SerializeElement(evaluatedElement)
// PST: ハッシュ出力でなくissuedElementを検証。
if issuedElement != W:
raise InvalidInputError
// PST: サーバーはclient_dataや他情報からredemptionRecordを構築してクライアントへ返せる。
return redemptionRecord
5. アルゴリズム
ユーザーエージェントは issuerAssociations(map)を持ちます。このmapのキーは origin topLevel であり、値は リストで、 origin のリストです。
発行者紐付けがトップレベル制限超過か判定するには、origin issuer と origin topLevel が与えられたとき、以下の手順を実行します:
-
issuerAssociations[topLevel] が 存在しない 場合、false を返す。
-
issuerAssociations[topLevel] が issuer を含む場合、false を返す。
-
issuerAssociations[topLevel] の サイズ が 2 未満なら false を返す。
-
true を返す。
発行者の紐付けを issuer(origin)と origin topLevel で行うには、以下の手順を実行します:
-
issuerAssociations[topLevel] が 存在しない場合、issuerAssociations[topLevel] に空の リスト を設定する。
-
issuer を issuerAssociations[topLevel] に追加する。
origin issuer が 指定のものに関連づいているか判定するには、origin topLevel で以下の手順を実行します:
-
issuerAssociations[topLevel] が 存在しない場合、false を返す。
-
issuerAssociations[topLevel] が issuer を含んでいる場合、true を返す。
-
false を返す。
ユーザーエージェントは redemptionTimes(map)を持ちます。キーは tuple (issuer, topLevel) であり、値は tuple (lastRedemption, penultimateRedemption) です。
引換タイムスタンプを記録するには、origin issuer と origin topLevel で次の手順を実行します:
-
currentTime を現在時刻に設定する。
-
previousRedemption を表現できる最も早い日時に設定する。
-
redemptionTimes[(issuer,topLevel)] が 存在するなら、previousRedemptionを tuple の lastRedemption に設定する。
-
set redemptionTimes[(issuer,topLevel)] に (currentTime, previousRedemption) を設定する。
1つ前の引換を参照するには、origin issuer と origin topLevel で次の手順を実行:
-
penultimateRedemption を表現できる最も早い日時に設定する。
-
redemptionTimes[(issuer,topLevel)] が 存在する場合は penultimateRedemption を tuple の penultimateRedemption に設定する。
-
penultimateRedemption を返す。
ユーザーエージェントは redemptionRecords(map)を持ちます。キーは tuple (issuer, topLevel)、値は tuple (record, expiration, signingKeys) です。
引換レコードを記録するには、origin issuer、origin topLevel、byte sequence header、duration lifetime で以下の手順を実行:
-
lifetimeが0ならreturn。
-
currentTime を 1970年1月1日UTCからのミリ秒として現在時刻に設定する。
-
expiration を currentTimeとlifetimeの和に設定する。
-
signingKeysをissuer に対し最新鍵の参照で得る。
-
redemptionRecords[(issuer, topLevel)] に (header, expiration, signingKeys) を設定する。
引換レコードを取得するには、origin issuer および origin topLevel で以下の手順を実行:
-
currentTime を 1970年1月1日UTCからのミリ秒で現在時刻に設定。
-
redemptionRecords[(issuer,topLevel)] が存在しない場合、null を返す。
-
(record, expiration, signingKeys) を redemptionRecords[(issuer,topLevel)] とする。
-
expiration が currentTime より小さい場合、null を返す。
-
currentSigningKeys を 最新鍵の参照で issuer に対して取得。
-
currentSigningKeys が signingKeys と異なれば null を返す。
-
record を返す。
ユーザーエージェントは pstKeyCommitments(map)を持ちます。キーは origin、値は key commitmentsです。
注: 全ユーザーエージェントが発行者からのkey commitmentを一定間隔で信頼できるインフラ経由で取得し、発行者とkey commitmentのマップをクライアントに配布することで異なるUA間で鍵を一貫させることが推奨されます。
key commitmentsの参照を origin issuer で行うには以下:
-
pstKeyCommitments[issuer] が 存在しない場合、null を返す。
-
issuerKeys を pstKeyCommitments[issuer] に設定。
-
このAPIでサポートする全 cryptoProtocolVersion について、 実装依存 順で次の手順:
-
issuerKeys[cryptoProtocolVersion] が 存在 する場合、その値を返す。
-
-
nullを返す。
注: cryptoProtocolVersion は本APIで使えるトークンの暗号バージョン識別子です。UAはサポートし好みのバージョンを順に選択してください。
最新鍵の参照を origin issuer で行うには:
-
commitment に key commitmentsの参照 を適用した結果を格納。
-
commitment が null の場合はnullを返す。
-
chosenKey をnullに設定。
-
currentTime を現在の日付時刻に設定。
-
commitment["keys"] の各 key について:
-
key["expiry"] < currentTime ならcontinue。
-
chosenKey がnullなら chosenKeyにkeyを設定。
-
key["expiry"] < chosenKey["expiry"] なら chosenKey=keyにする。
-
-
chosenKey を返す。
ユーザーエージェントは tokenStore(map)を持ち、キーは origin、値は リスト。リストの要素は storedToken(tuple (byte sequence, byte sequence))です。
トークンを挿入するには、 origin issuer、byte sequence token、byte sequence signingKey で次を実行:
-
新しい tuple storedToken を (token,signingKey)で生成。
-
tokenStore[issuer] が 存在しない場合、tokenStore[issuer] に空の リスト を設定。
-
storedToken を tokenStore[issuer] に追加。
トークンを取得するには、 origin issuer で次の手順:
-
tokenStore[issuer] が 存在しない場合、null。
-
tokenStore[issuer] のサイズがゼロならnull。
-
tokenStore[issuer] からランダムな要素を削除し、それを返す。
トークンを破棄するには、 origin issuer と byte sequence signingKey で以下を実行:
-
tokenStore[issuer] が 存在しない場合、return。
-
tokenStore[issuer] から、2番目の要素がsigningKeyでないすべての要素を削除する。
トークン数の取得は origin issuer で次を実行:
-
tokenStore[issuer] が 存在しない場合は0。
-
tokenStore[issuer] のサイズを返す。
最大バッチ数の取得は origin issuer で次を実行:
-
issuerKey を key commitments参照で得る。
-
issuerKeyがnullなら0。
-
issuerKey["batchsize"]を返す。
マスク済みトークンの生成は、key commitment issuerKeysと数値numTokensで以下を実行(戻り値はtuple(byte sequence, byte sequence)):
-
issueRequest を空の IssueRequest に設定する。
-
issueRequest["count"]に numTokens をセット。
-
blinds を空の byte sequenceにする。
-
次をnumTokens回繰り返す:
-
inputにランダムなbyte sequence をセット。
-
(blind, blindedElement)(tuple (byte sequence, byte sequence))をBlind関数で求める(blindedElementはX9.62アンコンプレストポイントでエンコード)。
-
blind を blinds に追加。
-
blindedElement を issueRequest["nonces"] に追加。
-
-
issueHeader を プロトコルメッセージ直列化で issueRequest を得る。
-
tuple (issueHeader, blinds) を返す。
トークンのアンマスクは、key commitment issuerKeys、byte文字列blinds、byte sequence response で以下を実行(返り値はリスト byte sequence):
-
input を空の byte sequence に設定。
-
evaluatedElements、blindedElements を空の リストに設定。
-
issueResponseにprotocol messageデシリアライズで responseを IssueResponseとして変換。
-
issueResponse["signed"] の各 nonce について:
-
pkS を issuerKeys["keys"][issueResponse["key_id"]]["Y"] に設定。
-
proof を issueResponse["proof"] に設定。
-
blindsList を空の リスト に設定。
-
blinds のlength が 0 より大きい間:
-
blind を blinds の先頭N要素、残りをblindsに設定(NはX9.62アンコンプレストポイント長)。
-
blind を blindsList に追加。
-
-
result(リストのbyte列)は PSTFinalizeに input, blindsList, evaluatedElements, blindedElements, pkS, proofを渡して得る。
-
result を返す。
requestのprivate
tokenプロパティ設定は、PrivateToken
privateToken と request request で次を実行:
-
request の private token operation を privateToken["
operation"]にセット。 -
privateToken["
operation"] が"token-request"の場合:-
Should request be allowed to use feature? で "
private-state-token-issuance" と request を評価してfalseなら "NotAllowedError"DOMExceptionを投げる。 -
以降の手順を中止。
-
-
Assert: privateToken["
operation"] は"token-redemption"または"send-redemption-record"であること。 -
Should request be allowed to use feature? で "
private-state-token-redemption" と request を評価しfalseなら、"NotAllowedError"DOMExceptionを投げる。 -
privateToken["
operation"] が"token-redemption"の場合:-
request の private token refresh policy に privateToken["
refreshPolicy"]をセット。 -
以降の手順を中止。
-
-
privateToken["
issuers"] の各 issuer について:-
issuerURL を URLパーサで issuer を解釈。
-
issuerURL が failure なら
TypeErrorを投げる。 -
issuerURL の scheme が HTTP(S) scheme以外なら
TypeErrorを投げる。 -
issuerOrigin を issuerURL の origin に設定。
-
issuerURL を request の private token issuers に追加する。
-
6. Fetchとの統合
6.1. 定義
RefreshPolicy
はリディーム(引換)リクエストに付随し、リディームの結果を以前返した未失効のredemption record(引換記録)にするか新たに作成するかを決定します。
enum {RefreshPolicy ,"none" };"refresh"
TokenVersion
は現在1のみ設定されています。現時点でこの仕様がサポートする唯一のバージョンです。
enum {TokenVersion };"1"
OperationType
はユーザーエージェントが実行しようとしている操作種別を表します。
enum {OperationType ,"token-request" ,"send-redemption-record" };"token-redemption"
PrivateToken
はfetchリクエスト作成に必要な情報を保持します。
dictionary {PrivateToken required TokenVersion ;version required OperationType ;operation RefreshPolicy = "none";refreshPolicy sequence <USVString >; };issuers
本仕様はRequestInit
辞書に新たなプロパティを追加します:
partial dictionary RequestInit {PrivateToken ; };privateToken
6.2. リクエストの修正
requestには、private token refresh
policy(型はRefreshPolicy
、初期値は"none")が関連付けられます。
requestには、private
token operation(型はOperationType)も保持します。
requestにはprivate token issuers(文字列リスト)も関連付けられます。
注: private
token refresh policyは、private token
operationが"token-redemption"以外のときは無視されます。private token
issuersは"send-redemption-record"以外は無視されます。private token
issuersは"send-redemption-record"のとき必ず指定され非空でなければなりません。
本仕様は新たに2つのポリシー制御フィーチャを定義します。いずれか1つがPrivate State Token操作ごとに適用されます。
ポリシー制御フィーチャ "private-state-token-issuance"
は"token-request"操作時に適用されます。この機能のデフォルト許可リストは
["self"]です。
ポリシー制御フィーチャ "private-state-token-redemption"
は"send-redemption-record"と"token-redemption"操作で適用。この機能のデフォルト許可リストは
["self"]です。
requestにはpstPretokens (nullまたはbyte sequence)も関連付けられます。
以下の手順を
new Request (input, init)
コンストラクタの28番目のステップ
("Set this's request to request")
の前に追加します:
RequestInit
initとRequest
request が与えられたとき、次の手順を実行:
-
もしinit["
privateToken"] が存在すれば:-
privateToken を init["
privateToken"] とする。 -
set private token properties for request from private token をprivateTokenと request で実行する。
-
6.3. http-network-or-cache fetchの修正
本仕様はhttp-network-or-cache fetchアルゴリズムへ以下の手順を、header listの修正前に追加します:
-
もしrequestのprivate token operation が null なら残りの手順を中止。
-
もしrequestのprivate token operationが
"token-request"なら:-
private state token issue request headers をhttpRequestに追加する。
-
以降の手順を中止。
-
-
もしrequestのprivate token operationが
"token-redemption"なら:-
private state token redemption request headers をhttpRequestに追加。
-
以降の手順を中止。
-
-
Assert: requestのprivate token operationは
"send-redemption-record"であること。 -
private state token redemption record headers をhttpRequestに追加する。
6.4. HTTP fetch ステップの修正
仕様はHTTP fetch アルゴリズムへ以下の手順を追加します(リダイレクトステータス判定前、「7. actualResponseのstatusがリダイレクトステータスなら…」直前):
-
issue response resultに、handling an issue response( request request、response actualResponse)の結果を格納する。
-
もしissue response resultがnetwork errorならissue response resultを返す。
-
redeem response resultにhandling a redeem response (request request、response actualResponse)の結果を格納する。
-
もしredeem response resultがnetwork errorならissue response resultを返す。
7. iframeとの統合
7.1. HTMLIframeElementのprivateTokenコンテント属性
iframe
要素はprivateTokenというコンテント属性を持ちます。IDL属性privateToken
は
privateToken
コンテント属性を
反映
します。
partial interface HTMLIFrameElement { [SecureContext ]attribute DOMString ; };privateToken
7.2. "create navigation params by fetching"手順の修正
次の手順はcreate navigation params by fetching の"25. 新たな navigation params を返す..."の直前に追加されます:
-
もし navigable の container が
iframe要素であり、かつprivateTokenコンテント属性を持つ場合、set private token properties for request from private token をnavigable のprivateToken およびrequest で実行する。
8. XMLHttpRequestとの統合
8.1. PrivateTokenの付加
XMLHttpRequest
には関連付けられたprivate state token(PrivateToken
オブジェクト)があり、そのリクエストに対してOperationType
を指定します。
partial interface XMLHttpRequest {undefined setPrivateToken (PrivateToken ); };privateToken
setPrivateToken(PrivateToken privateToken)
の手順は以下:
-
thisの stateが"opened"でなければ "
InvalidStateError"DOMExceptionを投げる。 - thisの
send()フラグが立っていれば、 throw "InvalidStateError"DOMExceptionを投げる。 - thisの private state token をprivateTokenにセット。
8.2. send()モンキーパッチ
send(body)
を以下のように修正します:
次の手順の後で:
req を新しい requestとして作成し、以下で初期化…
手順を追加:
-
set private token properties for request from private token を this の private state token および req で実行。
9. 発行プロトコル
このセクションは発行プロトコルを説明します。ユーザーエージェントと発行者双方の発行プロトコル手順が記載されています。
9.1. 発行リクエストの作成
let issueRequest= new Request( "https://example.issuer:1234/issuer_path" , { privateToken: { version: 1 , operation: "token-request" , } }); fetch( issueRequest);
private state token issue request headersを追加するには、request request で次を実行:
-
requestのclientがセキュアコンテキストでなければreturn。
-
topLevelをrequestのclientのトップレベルオリジンとする。
-
もしissuerとtopLevelの関連付けがトップレベル発行者数制限を超えるならreturn。
-
発行者紐付けでissuerとtopLevelを関連付け。
-
もしissuerの保有トークン数が500以上ならreturn。
-
issuerKeysをissuerへのkey commitments参照結果とする。
-
issuerKeysがnullならreturn。
-
signingKeyをissuerへの最新鍵参照結果とする。
-
トークン破棄でissuerとsigningKeyを渡す。
-
numTokensを(issuerのmax batch sizeまたは実装定義の制限(推奨100)の小さい方)とする。
-
(issueHeader, pretokens)をマスク済みトークン生成(issuerKeys, numTokens)で得る。
-
requestのcache modeを
"no-store"にする。 -
requestのpstPretokensをpretokensに設定する。
-
base64EncodedTokensをissueHeaderをbase64符号化([RFC4648])したものとする。
-
cryptoProtocolVersionを利用する暗号プロトコルバージョンとする。
-
構造化ヘッダ値設定に (
Sec-Private-State-Token, base64EncodedTokens) およびrequestのheader listを指定して実行。 -
構造化ヘッダ値設定に (
Sec-Private-State-Token-Crypto-Version, cryptoProtocolVersion) およびrequestのheader listを指定して実行。
Sec-Private-State-Token: <masked tokens encoded as base64 string> Sec-Private-State-Token-Crypto-Version: <cryptographic protocol version, VOPRF>
9.2. 発行者によるトークン署名
このセクションでは、発行者サーバーで行われるトークンの署名処理について説明します。VOPRFは、使用する鍵の選択によって6種類の値のいずれかのみを符号化できます。
発行者は、自身の秘密鍵を用いて、Sec-Private-State-Tokenリクエストヘッダー値で受け取ったマスク済トークンに対し、発行リクエストで渡されたその他の情報に依存する値で署名を行います。発行者は、リクエストヘッダーのSec-Private-State-Token-Crypto-Versionで指定された暗号プロトコルを用います。発行者は、 署名済みトークンを、ベース64[RFC4648]バイト列としてエンコードし、Sec-Private-State-Tokenレスポンスヘッダー値で返します。
Sec-Private-State-Token: <token encoded as base64 string>
9.3. 発行レスポンスの処理
発行レスポンスの処理では、request request および response response が与えられたとき、次の手順を実行します:
-
もし request の ヘッダーリスト が 含んでいない Sec-Private-State-Token の場合、null を返す。
-
もし response の ヘッダーリスト が 含んでいない Sec-Private-State-Token の場合、ネットワークエラー を返す。
-
header を、取得する 結果としての Sec-Private-State-Token を response の ヘッダーリスト から得たものとする。
-
もし header が空なら、戻る。
-
削除する Sec-Private-State-Token を response の ヘッダーリスト から行う。
-
issuerKeys を issuer の鍵コミットメントを照会する 結果とする。
-
もし issuerKeys が null なら、戻る。
-
pretokens を request の pstPretokens とする。
-
もし pretokens が null なら、戻る。
-
rawResponse を header の [RFC4648] に基づく base64 デコード版とする。
-
unmasked tokens を 応答トークンのマスク解除 を issuerKeys, pretokens, および rawResponse に対して行った結果とする。
-
もし unmasked tokens が null なら、ネットワークエラー を返す。
-
signingKey を issuer のための 最新の鍵を照会する 結果とする。
-
各 unmasked tokens の中の token について、以下の手順を実行する:
-
トークンを挿入する を issuer, token, および signingKey に対して行う。
-
-
戻る。
10. トークンの引換
ユーザーエージェントがあるトップレベルオリジンへナビゲーションしたとき、このトップレベルオリジンまたはその上で埋め込まれたサードパーティサイトは、ユーザーエージェント内に格納された特定発行者のトークンを引換し、そのトークンに符号化されたデータを取得できます。
'none'です。
let redemptionRequest= new Request( 'https://example.issuer:1234/redemption_path' , { privateToken: { version: 1 , operation: 'token-redemption' , refreshPolicy: { 'none' , 'refresh' } } });
引換ヘッダーの設定は、request request および RedeemRequest record で次を実行:
-
redemptionRequest をプロトコルメッセージ直列化でrecordを変換したものとする。
-
cryptoProtocolVersion を使用する暗号プロトコルバージョンとする。
-
token-lifetime を redemption recordの有効期間(秒単位)とする。
-
構造化フィールド値設定で (
Sec-Private-State-Token,redemptionRequest) を request のヘッダーリストに設定。 -
構造化フィールド値設定で (
Sec-Private-State-Token-Crypto-Version,cryptoProtocolVersion) を requestのヘッダーリストに設定。 -
オプションで、(
Sec-Private-State-Token-Lifetime,token-lifetime) を構造化フィールド値設定でrequestのヘッダーリストにセットできる。 -
requestのcache modeを
"no-store"にする。
private state token redemption request headersを追加するには、request request で次を実行:
-
topLevel を requestのclientのトップレベルオリジンとする。
-
requestのclientがセキュアコンテキストでなければreturn。
-
もしissuerとtopLevelの関連付けがトップレベル発行者数制限を超えるならreturn。
-
発行者紐付けでissuerとtopLevelを関連付け。
-
requestのprivate token refresh policyが
"none"の場合: -
penultimateRedemptionをissuerとtopLevelで1つ前の引換参照で得る。
-
penultimateRedemptionが実装依存時間(推奨48時間)より小さいならエラー返す。
-
commitmentsをissuerでkey commitments参照で得る。
-
commitmentsがnullならreturn。
-
tokenをissuerでトークン取得の結果とする。
-
tokenがnullならreturn。
-
redeemRequest を空の RedeemRequestとする。
-
redeemRequest["token"] に token をセットする。
-
引換ヘッダー設定をrequestとrecordで実施。
10.1. リディームレスポンスの処理
リディームレスポンスの処理を行うには、request request と response response が与えられたとき、以下の手順を実行する:
-
もし request の ヘッダーリスト 含んでいない Sec-Private-State-Token の場合、null を返す。
-
もし response の ヘッダーリスト 含んでいない Sec-Private-State-Token の場合、ネットワークエラー を返す。
-
rawHeader を、取得する 結果としての Sec-Private-State-Token を response の ヘッダーリスト から得たものとする。
-
もし rawHeader が空なら、null を返す。
-
rawResponse を rawHeader の [RFC4648] に基づく base64 デコード版とする。
-
header を プロトコルメッセージをデシリアライズする 結果としての rawHeader を RedeemResponse として扱ったものとする。
-
削除する Sec-Private-State-Token を response の ヘッダーリスト から行う。
-
lifetime を表現可能な最大の期間に設定する。
-
もし response の ヘッダーリスト contains Sec-Private-State-Token-Lifetime レスポンスヘッダー が含まれている場合、lifetime をその値に設定する。
-
削除する Sec-Private-State-Token-Lifetime を response の ヘッダーリスト から行う。
-
topLevel を request の client の top-level origin とする。
-
償還タイムスタンプの記録を実行する を issuer と topLevel に対して行う。
-
償還記録を記録する を issuer、 topLevel、header、および lifetime に対して行う。
Note: Redemption Record は HTTP Only であり、JavaScript からは Private State Token Fetch API 経由でのみアクセス・送信されます。Redemption Record は発行者からの任意のバイト列として扱われ、ダウンストリーム利用者に意味がある場合もあります。
10.2. Redemption Record(引換レコード)
通信負荷を減らすため、ユーザーエージェントはリディームレスポンスのSec-Private-State-Tokenヘッダーで返されたバイト列をキャッシュする場合があります。これらのバイト列はRedemption
Record(引換レコード)と呼ばれます。ユーザーエージェントはこれらのレコードを保存し、後続のリクエストで、その有効性を確認可能なオリジンへ送信する場合があります。発行者はリディームレスポンス内で任意でSec-Private-State-Token-Lifetimeヘッダーを含めることもできます。この値はRedemption
Recordの有効期限(秒数)であり、Sec-Private-State-Token-Lifetime
HTTPレスポンスヘッダーで指定されます。
Redemption Recordはバイト列(byte sequence)です。
Private State Token APIには 'send-redemption-record' 操作があり、private state token redemption record
headers追加 ができます。この操作は、以前に記録された引換レコード(リディームレスポンスの処理で得たもの)を利用します。
private state token redemption record headers追加を行うには、request request で次を実行:
-
もし request の client が セキュアコンテキスト でなければ、手順を中止する。
-
topLevel を request の client の トップレベルオリジン とする。
-
private token issuersの各issuerについて:
-
issuerURL を URLパーサに issuer を渡して結果とする。
-
issuerURL がfailureのとき、手順を中止する。
-
issuerURL の scheme が HTTP(S) scheme でなければ中止する。
-
issuerOrigin を issuerURL の origin とする。
-
issuerOrigin が 信頼できるオリジン でなければ、手順を中止する。
-
-
records_per_issuer を USVString をキー、redemption records を値とする map として初期化。
-
private token issuers の各 issuer について:
-
record を 引換レコード取得にissuerとtopLevel を渡して得る。
-
record が null なら 続ける。
-
records_per_issuer[issuer] に record をセットする。
-
-
records_per_issuer が空であれば、この手順を中止する。
-
headerItems を structured headers list [RFC8941]とする。
-
records_per_issuer の各 issuer -> record について:
-
serializedIssuer を issuer のシリアライズ結果とする。
-
serializedRecord を record のシリアライズ結果とする。
-
serializedIssuer と serializedRecord のペアを headerItems に追加。
-
-
serializedHeaderItems を headerItems のシリアライズ結果とする。
-
serializedHeaderItems が null なら手順を中止する。
-
Sec-Redemption-Recordに serializedHeaderItems をセットする。
10.3. Documentの変更
partial interface Document {Promise <boolean >hasPrivateToken (USVString );issuer Promise <boolean >hasRedemptionRecord (USVString ); };issuer
11. クエリアイピーアイ
11.1. トークンクエリ
Document
docに対してUSVString
issuerを指定して呼び出された場合、hasPrivateToken(issuer)メソッドは以下の手順を実行する必要があります:
-
pを新たなpromiseとする。
-
docがfully activeでなければ、pを "
InvalidStateError"DOMExceptionでrejectし、pを返す。 -
globalをdocのrelevant global objectとする。
-
globalがセキュアコンテキストでなければ、pを "
NotAllowedError"DOMExceptionでrejectし、pを返す。 -
parsedURLをissuerでURLパーサを実行した結果とする。
-
parsedURLがfailureなら、pを "
TypeError"DOMExceptionでrejectし、pを返す。 -
originをparsedURLのoriginとする。
-
topLevelをdocのトップレベルオリジンとする。
-
次の手順を並列で実行:
-
issuerとtopLevelの関連付けがトップレベル発行者数制限を超える場合、グローバルタスクをキューし、networking task sourceでglobal、pを "
NotAllowedError"DOMExceptionでrejectしreturn。 -
発行者紐付けでoriginとtopLevelを関連付ける。
-
key commitmentsの参照でoriginを参照。コミットメントがある場合、コミットメント以外の鍵で署名されたトークンを破棄。
-
グローバルタスクをキューしてnetworking task sourceでglobal、pをトークンが与えられたissuerでストアされていればtrue、そうでなければfalseで resolve。
-
-
pを返す。
注: このクエリはユーザーエージェントの状態を変更します。issuer引数を現在のオリジンに関連付けます。仕様では1つのオリジンにつき最大2つのissuerだけを関連付けられるようにしています。これはユーザーがどのissuerのトークンを持っているかを経由した情報漏洩を防ぐためです。トークンクエリを行うと古いトークンの削除が発生することにも注意してください。
11.2. 引換レコードクエリ
Document
doc に対して USVString
issuer を指定して呼び出された場合、hasRedemptionRecord(issuer)
メソッドは以下の手順を実行する必要があります:
-
pを新たなpromiseとする。
-
docがfully activeでなければ、pを "
InvalidStateError"DOMExceptionでrejectし、pを返す。 -
globalをdocのrelevant global objectとする。
-
globalがセキュアコンテキストでなければ、pを "
NotAllowedError"DOMExceptionでrejectし、pを返す。 -
parsedURLをissuerでURLパーサを実行した結果とする。
-
parsedURLがfailureなら、pを "
TypeError"DOMExceptionでrejectし、pを返す。 -
originをparsedURLのoriginとする。
-
topLevelをdocのトップレベルオリジンとする。
-
次の手順を並列で実行:
-
originがtopLevelと関連付けられていなければ、グローバルタスクをキューしてnetworking task sourceでglobal、 pをfalseでresolveしreturn。
-
key commitmentsの参照でorigin。存在すれば最新コミットメント以外の鍵で署名済トークンを破棄。
-
グローバルタスクをキューしてnetworking task sourceでglobal、 pを該当issuer・トップレベルペアのために 引換レコードが存在していればtrue、なければfalseでresolve。
-
-
pを返す。
注: トークンクエリ同様、引換クエリもユーザーエージェント状態を変更する場合がありますが、token queryと異なり、issuerをトップレベルオリジンに関連付けません。引換クエリの答えがストア済みトークンのissuerに関する情報を漏洩しないからです。トークンクエリ同様、古いトークンの削除も行います。
11.3. PSTデータの消去
ユーザーインターフェースガイドライン(storage仕様より)に従う必要があります。ユーザーエージェントはPSTデータをストレージから消去するためのインターフェースを提供すべきです。
12. Private State Token HTTPヘッダーフィールド
12.1. 'Sec-Private-State-Token' ヘッダーフィールド
Sec-Private-State-Token リクエストヘッダーは、発行時に署名なし・マスク済トークンの集合を送信します。引換時は、署名済かつアンマスク済みのトークン1つと関連する引換メタデータを送ります。
Sec-Private-State-Token レスポンスヘッダーは 署名済み・マスク済みトークンの集合を送信します。引換時は生成されたばかりの署名済み引換レコードを送信します。
このヘッダーはStructured Headerであり、その値は文字列 [RFC8941]でなければなりません。
ヘッダーのABNFは次の通りです:
Sec-Private-State-Token = sf-string
12.2. 'Sec-Private-State-Token-Lifetime' ヘッダーフィールド
Sec-Private-State-Token-Lifetime
レスポンスヘッダーは、関連付けられた引換レコードを含むSec-Private-State-Tokenレスポンスヘッダーの有効期限を示します。有効期限は秒単位です。
このヘッダーはStructured Headerであり、その値は整数 [RFC8941]でなければなりません。
ヘッダーのABNFは次の通り:
Sec-Private-State-Token-Lifetime = sf-integer
12.3. 'Sec-Private-State-Token-Crypto-Version' ヘッダーフィールド
Sec-Private-State-Token-Crypto-Version
ヘッダーフィールドは、Private State Tokenの暗号プロトコルバージョンを示します。
このヘッダーはStructured Headerで、その値は文字列 [RFC8941]でなければなりません。
ヘッダーのABNFは次の通りです:
Sec-Private-State-Token-Crypto-Version = sf-string
12.4. 'Sec-Redemption-Record' ヘッダーフィールド
Sec-Redemption-Record
リクエストヘッダーフィールドは、以前の引換操作で得た引換レコードのキャッシュを送信します。
このヘッダーはStructured Headerで、その値は文字列 [RFC8941]でなければなりません。
ヘッダーのABNFは次の通りです:
Sec-Redemption-Record = sf-string
13. プライバシーに関する考慮事項
13.1. リンク不可性
暗号プロトコル[VOPRF]はマスクされた署名を提供します。 引換時、発行者は提供されたトークン上の自分の署名は識別できますが、その署名がどの時点・どの文脈で行われたかは特定できません。これによって発行者が自身の発行を他オリジンでの引換と関連付けることを防ぎます。発行者は利用者の訪問オリジンに関する集計情報しか知ることができません。
13.2. 符号化情報の制限
ユーザーエージェントは、発行者ごとに一度に保持できるユニークな鍵の数を制限すべきです。制限がなければ、発行者は利用者ごとにユニークな鍵を利用して匿名性を破ることができます。[VOPRF]では鍵は6個に制限されています。
発行者は、さまざまな「ラベル」(例:信頼レベルやその他のアンチ不正シグナル)を表現するために異なる鍵を利用できます。発行者はこの対応関係を理解し、ラベル情報をトークン受取側に共有する責任を持ちます。これによって悪意あるリバースエンジニアリングの難易度が上がり、ラベルが人間可読な場合でもプライバシー保護に役立ちます。[VOPRF]使用時、発行者は6つのラベルを鍵で表現できます。
13.2.1. 潜在的攻撃:サイドチャネルフィンガープリンティング
もし発行者がネットワークレベルのフィンガープリンティングや他のサイドチャネルを用いて、引換時のユーザーエージェントと発行時のユーザーエージェントを結びつけられると、プライベートステートトークンAPI自体が記録・公開する情報は限定されていても、リンク不可性は失われます。
13.3. クロスサイト情報転送
プライベートステートトークンはファーストパーティ間で限定的情報を転送します。基礎暗号プロトコルで各トークンに含めうる情報量は小さいものです。しかし、1ページ内で複数回の引換を許すと、ドメインAのユーザーUの1pクッキーをトークン情報チャネルでエンコードし、ドメインBでデコードして、BがAのユーザークッキーを知る可能性が出ます。領域を越える通信チャネルの問題とは別に、発行済みトークンissuer全体を特定しようとする悪意あるレディーマの攻撃も同様の緩和策で対応できます。
13.3.1. 緩和策:動的発行/引換上限
これへの対策として、本仕様では発行と引換双方の回数を制限します。発行操作にはユーザーによる発行サイトアクティベーションが必須です。また、実装依存(通常は48時間)の時間枠内で3回目の引換は許されません。
13.3.2. 緩和策:サイトごと発行者上限
1つのオリジンに許可される発行者数が増えるほど、識別情報リーク率も高まります。不正利用防止のため、ユーザーエージェントはトップレベルオリジンごとに最大2つまで発行者を関連付けます。Token Query APIもこの制限が適用されます。詳しくは§11.1 トークンクエリ。
14. セキュリティに関する考慮事項
14.1. トークン枯渇防止
悪意あるオリジンがユーザーエージェント内のすべてのトークンを引換して枯渇させようとするかもしれません。これを防ぐため、本仕様では引換操作数を制限します。特定オリジン文脈内では、初回2回までは引換可能ですが、3回目は最初の引換から実装依存(通常48時間)より長い時間経過後でなければ許可されません。
14.2. 発行者枯渇防止
競合スクリプトがhasPrivateToken(issuer)を競って呼び出し、他者より先に自身のissuerを
issuerAssociations map
に追加しようとするかもしれません(二つまでしか登録できないため)。これを制御するには、トップレベルオリジンで他のJavaScript読み込み前に最大2種類の
hasPrivateToken(issuer)を呼ぶことで優先発行者が確保できます。
14.3. 二重支払い防止
発行者は各トークンが一度しか使われないことを検証できます。すべての引換が同じ発行元に送信されるからです。悪意あるマルウェアがユーザーのトークン全てを抜き取っても、トークンは時間経過と共に消費されます。発行者側で一度に署名するトークン数を減らすことでこのリスクを緩和できます。
15. IANAに関する考慮事項
本ドキュメントは、Sec-Private-State-Token, Sec-Private-State-Token-Lifetime, Sec-Private-State-Token-Crypto-Version HTTPリクエストヘッダの定義・permanent message header field registry([RFC9110])への登録を意図しています。
15.1. 'Sec-Private-State-Token' ヘッダーフィールド
ヘッダーフィールド名: Sec-Private-State-Token
対応プロトコル: http
ステータス: standard
著者/変更管理者: IETF
仕様書: 本仕様 (§12.1 'Sec-Private-State-Token' ヘッダーフィールド)
15.2. 'Sec-Private-State-Token-Lifetime' ヘッダーフィールド
ヘッダーフィールド名: Sec-Private-State-Token-Lifetime
対応プロトコル: http
ステータス: standard
著者/変更管理者: IETF
仕様書: 本仕様 (§ 12.2 'Sec-Private-State-Token-Lifetime' ヘッダーフィールド)
15.3. 'Sec-Private-State-Token-Crypto-Version' ヘッダーフィールド
ヘッダーフィールド名: Sec-Private-State-Token-Crypto-Version
対応プロトコル: http
ステータス: standard
著者/変更管理者: IETF
仕様書: 本仕様 (§ 12.3 'Sec-Private-State-Token-Crypto-Version' ヘッダーフィールド)
15.4. 'Sec-Redemption-Record' ヘッダーフィールド
ヘッダーフィールド名: Sec-Redemption-Record
対応プロトコル: http
ステータス: standard
著者/変更管理者: IETF
仕様書: 本仕様 (§ 12.4 'Sec-Redemption-Record' ヘッダーフィールド)
謝辞
Alex Kallam、Charlie Harrison、Chris Fredrickson、David Van Cleve、Dylan Cutler、 Eric Trouton、Johann Hofmann、Kaustubha Govind、Mike Taylor、Ryan Kalla、Sam Schlesinger 各氏のご協力に感謝します。この仕様のレビューおよび指導にChris Wilson氏へ感謝します。