Internet-Draft | Rate-Limited Tokens | April 2022 |
Hendrickson, et al. | Expires 7 October 2022 | [Page] |
This document specifies a variant of the Privacy Pass issuance protocol that allows for tokens to be rate-limited on a per-origin basis. This enables origins to use tokens for use cases that need to restrict access from anonymous clients.¶
This note is to be removed before publishing as an RFC.¶
Source for this draft and an issue tracker can be found at https://github.com/tfpauly/privacy-proxy.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 7 October 2022.¶
Copyright (c) 2022 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
This document specifies a variant of the Privacy Pass issuance protocol (as defined in [ARCH]) that allows for tokens to be rate-limited on a per-origin basis. This enables origins to use tokens for use cases that need to restrict access from anonymous clients.¶
The base Privacy Pass issuance protocol [ISSUANCE] defines stateless anonymous tokens, which can either be publicly verifiable or not.¶
This variant build upon the publicly verifiable issuance protocol that uses RSA Blind Signatures [BLINDSIG], and allows tokens to be rate-limited on a per-origin basis. This means that a client will only be able to receive a limited number of tokens associated with a given origin server within a fixed period of time.¶
This issuance protocol registers the Rate-Limited Blind RSA token type (Section 11.1), to be used with the PrivateToken HTTP authentication scheme defined in [AUTHSCHEME].¶
A client that wishes to keep its IP address private can hide its IP address using a proxy service or a VPN. However, doing so severely limits the client's ability to access services and content, since servers might not be able to enforce their policies without a stable and unique client identifier.¶
Privacy Pass tokens in general allow clients to provide anonymous attestation of various properties. The tokens generated by the base issuance protocol ([ISSUANCE]) can be used to verify that a client meets a particular bar for attestation, but cannot be used by a redeeming server to rate-limit specific clients.¶
There are several use cases for rate-limiting anonymous clients that are common on the Internet. These routinely use client IP address tracking, among other characteristics, to implement rate-limiting.¶
One example of this use case is rate-limiting website accesses to a client to help prevent fraud. Operations that are sensitive to fraud, such as account creation on a website or logging into an account, often employ rate-limiting as a defense-in-depth strategy. Additional verification can be required by these pages when a client exceeds a set rate-limit.¶
Another example of this use case is a metered paywall, where an origin limits the number of page requests from each unique user over a period of time before the user is required to pay for access. The origin typically resets this state periodically, say, once per month. For example, an origin may serve ten (major content) requests in a month before a paywall is enacted. Origins may want to differentiate quick refreshes from distinct accesses.¶
For some applications, the basic issuance protocol from [BASIC-ISSUANCE] could be used to implement rate limits. In particular, the 'Joint Attester and Issuer' model from [ARCH] could be used to restrict the number of tokens issued to individual clients over a time window. However, in this deployment model, the Attester and Issuer would learn all origins used by a participating client. In some cases this might be a significant portion of browsing history. The issuance protocol defined in this document employs the 'Split Origin, Attester, Issuer' model to combat this, where the issuer would know all per-origin policies, and the attester would mantain per-client state without knowing all origins a client visits.¶
For rate-limited token issuance, the Attester, Issuer, and Origin as defined in [ARCH] each have partial knowledge of the Client's identity and actions, and each entity only knows enough to serve its function (see Section 2 for more about the pieces of information):¶
Since an Issuer enforces policies on behalf of Origins, a Client is required to reveal the Origin's identity to the delegated Issuer. It is a requirement of this protocol that the Attester not learn the Origin's identity so that, despite knowing the Client's identity, an Attester cannot track and concentrate information about Client activity.¶
An Issuer expects an Attester to verify its Clients' identities correctly, but an Issuer cannot confirm an Attester's efficacy or the Attester-Client relationship directly without learning the Client's identity. Similarly, an Origin does not know the Attester's identity, but ultimately relies on the Attester to correctly verify or authenticate a Client for the Origin's policies to be correctly enforced. An Issuer therefore chooses to issue tokens to only known and reputable Attesters; the Issuer can employ its own methods to determine the reputation of a Attester.¶
An Attester is expected to employ a stable Client identifier, such as an IP address, a device identifier, or an account at the Attester, that can serve as a reasonable proxy for a user with some creation and maintenance cost on the user.¶
For the Issuance protocol, a Client is expected to create and maintain stable and explicit secrets for time periods that are on the scale of Issuer policy windows. Changing these secrets arbitrarily during a policy window can result in token issuance failure for the rest of the policy window; see Section 5.1.1 for more details. A Client can use a service offered by its Attester or a third-party to store these secrets, but it is a requirement of this protocol that the Attester not be able to learn these secrets.¶
The privacy guarantees of this issuance protocol, specifically those around separating the identity of the Client from the names of the Origins that it accesses, are based on the expectation that there is not collusion between the entities that know about Client identity and those that know about Origin identity. Clients choose and share information with Attesters, and Origins choose and share policy with Issuers; however, the Attester is generally expected to not be colluding with Issuers or Origins. If this occurs, it can become possible for an Attester to learn or infer which Origins a Client is accessing, or for an Origin to learn or infer the Client identity. For further discussion, see Section 9.5.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
Unless otherwise specified, this document encodes protocol messages in TLS notation from [TLS13], Section 3.¶
This draft includes pseudocode that uses the functions and conventions defined in [HPKE].¶
Encoding an integer to a sequence of bytes in network byte order is described using the function "encode(n, v)", where "n" is the number of bytes and "v" is the integer value. The function "len()" returns the length of a sequence of bytes.¶
The following terms are defined in [ARCH] and are used throughout this document:¶
The following terms are defined in [AUTHSCHEME], which defines the interactions between clients and origins:¶
Additionally, this document defines several terms that are unique to the rate-limited issuance protocol:¶
Issuers MUST provide three parameters for configuration:¶
NameKey
structure as defined below to use when encrypting the Origin
Name in issuance requests. This parameter uses resource media type "application/issuer-name-key".
The Npk parameter corresponding to the HpkeKdfId can be found in [HPKE].¶
opaque HpkePublicKey[Npk]; // defined in RFC9180 uint16 HpkeKemId; // defined in RFC9180 uint16 HpkeKdfId; // defined in RFC9180 uint16 HpkeAeadId; // defined in RFC9180 struct { uint8 key_id; HpkeKemId kem_id; HpkePublicKey public_key; HpkeKdfId kdf_id; HpkeAeadId aead_id; } NameKey;¶
The Issuer parameters can be obtained from an Issuer via a directory object, which is a JSON object whose field names and values are raw values and URLs for the parameters.¶
Field Name | Value |
---|---|
issuer-policy-window | Issuer Policy Window as a JSON number |
issuer-request-uri | Issuer Request URI resource URL as a JSON string |
origin-name-key-uri | Origin Name Key URI resource URL as a JSON string |
As an example, the Issuer's JSON directory could look like:¶
{ "issuer-token-window": 86400, "issuer-request-uri": "https://issuer.example.net/token-request" "origin-name-key-uri": "https://issuer.example.net/name-key", }¶
Issuer directory resources have the media type "application/json" and are located at the well-known location /.well-known/token-issuer-directory.¶
Clients receive challenges for tokens, as described in [AUTHSCHEME].¶
For the rate-limited token issuance protocol described in this document, the name of the origin is sent in an encrypted message from the Client to the Issuer. If the TokenChallenge.origin_info field contains a single origin name, that origin name is used. If the origin_info field contains multiple origin names, the client selects the single origin name that presented the challenge. If the origin_info field is empty, the encrypted message is the empty string "".¶
The HTTP authentication challenge also SHOULD contain the following additional attribute:¶
This section describes the Issuance protocol for a Client to request and receive a token from an Issuer. Token issuance involves a Client, Attester, and Issuer, with the following steps:¶
The Issuance protocol is designed such that Client, Attester, and Issuer learn only what is necessary for completing the protocol; see Section 8.3 for more details.¶
The Issuance protocol has a number of underlying cryptographic dependencies for operation:¶
Clients and Issuers are required to implement all of these dependencies, whereas Attesters are required to implement ECDSA signature with key blinding support.¶
The Issuance protocol requires each participating endpoint to maintain some necessary state, as described in this section.¶
A Client is required to have the following information, derived from a given TokenChallenge:¶
Clients maintain a stable Client Key that they use for all communication with a specific Attester. Client Key is a public key, where the corresponding private key Client Secret is known only to the client.¶
If the client loses this (Client Key, Client Secret), they may generate a new tuple. The Attester will enforce if a client is allowed to use this new Client Key. See Section 5.1.2 for details on this enforcement.¶
Clients also need to be able to generate an Anonymous Origin ID value that corresponds to the Origin Name, to send in requests to the Attester.¶
Anonymous Origin ID MUST be a stable and unpredictable 32-byte value computed by the Client. Clients MUST NOT change this value across token requests for the same Origin Name. Doing so will result in token issuance failure (specifically, when an Attester rejects a request upon detecting two Anonymous Origin ID values that map to the same Origin).¶
One possible mechanism for implementing this identifier is for the Client to store a mapping between the Origin Name and a randomly generated Anonymous Origin ID for future requests. Alternatively, the Client can compute a PRF keyed by a per-client secret (Client Secret) over the Origin Name, e.g., Anonymous Origin ID = HKDF(secret=Client Secret, salt="", info=Origin Name).¶
An Attester is required to maintain state for every authenticated Client. The mechanism of identifying a Client is specific to each Attester, and is not defined in this document. As examples, the Attester could use device-specific certificates or account authentication to identify a Client.¶
Attesters must enforce that Clients don't change their Client Key frequently, to ensure Clients can't regularily evade the per-client policy as seen by the issuer. Attesters MUST NOT allow Clients to change their Client Key more than once within a policy window, or in the subsequent policy window after a previous Client Key change. Alternative schemes where the Attester stores the encrypted (Client Key, Client Secret) tuple on behalf of the client are possble but not described here.¶
Attesters are expected to know the Issuer Policy Window for any Issuer Name to which they allow access. This information can be retrieved using the URIs defined in Section 3.¶
For each Client-Issuer pair, an Attester maintains a policy window start and end time for each Issuer from which a Client requests a token.¶
For each tuple of (Client Key, Anonymous Origin ID, policy window), the Attester maintains the following state:¶
Issuers maintain a stable Issuer Origin Secret that they use in calculating values returned to the Attester for each origin. If this value changes, it will open up a possibility for Clients to request extra tokens for an Origin without being limited, within a policy window. See Section 10.1 for details about generating and rotating the Issuer Origin Secret.¶
Issuers are expected to have the private key that corresponds to Origin Name Key, which allows them to decrypt the Origin Name values in requests.¶
Issuers also need to know the set of valid Token Key public keys and corresponding private key, for each Origin Name that is served by the Issuer. Origins SHOULD update their view of the Token Key regularly to ensure that Client requests do not fail after Token Key rotation.¶
The Issuance protocol defines four new HTTP headers that are used in requests and responses between Clients, Attesters, and Issuers (see Section 11.2).¶
The "Sec-Token-Origin" is an Item Structured Header [RFC8941]. Its value MUST be a Byte Sequence. This header is sent both on Client-to-Attester requests (Section 5.3) and on Issuer-to-Attester responses (Section 5.5). Its ABNF is:¶
Sec-Token-Origin = sf-binary¶
The "Sec-Token-Client" is an Item Structured Header [RFC8941]. Its value MUST be a Byte Sequence. This header is sent on Client-to-Attester requests (Section 5.3), and contains the bytes of Client Key. Its ABNF is:¶
Sec-Token-Client = sf-binary¶
The "Sec-Token-Request-Blind" is an Item Structured Header [RFC8941]. Its value MUST be a Byte Sequence. This header is sent on Client-to-Attester requests (Section 5.3), and contains a per-request nonce value. Its ABNF is:¶
Sec-Token-Request-Blind = sf-binary¶
The "Sec-Token-Limit" is an Item Structured Header [RFC8941]. Its value MUST be an Integer. This header is sent on Issuer-to-Attester responses (Section 5.5), and contains the number of times a Client can retrieve a token for the requested Origin within a policy window, as set by the Issuer. Its ABNF is:¶
Sec-Token-Limit = sf-integer¶
The Client and Attester MUST use a secure and Attester-authenticated HTTPS connection. They MAY use mutual authentication or mechanisms such as TLS certificate pinning, to mitigate the risk of channel compromise; see Section 8 for additional about this channel.¶
Requests to the Attester need to indicate the Issuer Name to which issuance requests will be forwarded. Attesters SHOULD provide Clients with a URI template that contains one variable that contains the Issuer Name, "issuer", using Level 3 URI template encoding as defined in Section 1.2 of [RFC6570].¶
An example of an Attester URI templates is shown below:¶
https://attester.net/token-request{?issuer}¶
Attesters and Clients MAY agree on other mechanisms to specify the Issuer Name in requests.¶
The Client first creates an issuance request message for a random value nonce
using the input TokenChallenge challenge
and the Issuer key identifier key_id
as follows:¶
nonce = random(32) context = SHA256(challenge) token_input = concat(0x0003, nonce, context, key_id) blinded_msg, blind_inv = rsabssa_blind(pkI, token_input)¶
The Client then uses Client Key to generate its one-time-use request public
key request_key
and blind request_blind
as described in Section 7.1.¶
The Client then encrypts the origin name using Origin Name Key, producing
encrypted_origin_name
as described in Section 6.¶
Finally, the Client uses Client Secret to produce request_signature
as described in Section 7.1.2.¶
The Client then constructs a TokenRequest structure. This TokenRequest structure is based on the publicly verifiable token issuance path in [ISSUANCE], adding fields for the encrypted origin name and request signature.¶
struct { uint16_t token_type = 0x0003; uint8_t token_key_id; uint8_t blinded_msg[Nk]; uint8_t request_key[49]; uint8_t origin_name_key_id[32]; uint8_t encrypted_origin_name<1..2^16-1>; uint8_t request_signature[96]; } TokenRequest;¶
The structure fields are defined as follows:¶
The Client then generates an HTTP POST request to send through the Attester to the Issuer, with the TokenRequest as the body. The media type for this request is "message/token-request". The Client includes the "Sec-Token-Origin" header, whose value is Anonymous Origin ID; the "Sec-Token-Client" header, whose value is Client Key; and the "Sec-Token-Request-Blind" header, whose value is request_blind. The Client sends this request to the Attester's proxy URI. An example request is shown below, where Nk = 512, the Issuer Name is "issuer.net", and the Attester URI template is "https://attester.net/token-request{?issuer}"¶
:method = POST :scheme = https :authority = attester.net :path = /token-request?issuer=issuer.net accept = message/token-response cache-control = no-cache, no-store content-type = message/token-request content-length = <Length of TokenRequest> sec-token-origin = Anonymous Origin ID sec-token-client = Client Key sec-token-request-blind = request_blind <Bytes containing the TokenRequest>¶
If the Attester detects a token_type in the TokenRequest that it does not recognize or support, it MUST reject the request with an HTTP 400 error.¶
The Attester also checks to validate that the origin_name_key_id in the client's TokenRequest matches a known Origin Name Key public key for the Issuer. For example, the Attester can fetch this key using the API defined in Section 3. This check is done to help ensure that the Client has not been given a unique key that could allow the Issuer to fingerprint or target the Client. If the key does not match, the Attester rejects the request with an HTTP 400 error. Note that this can lead to failures in the event of Issuer Origin Name Key rotation; see Section 9 for considerations.¶
The Attester finally validates the Client's stable mapping request as described in Section 7.2. If this fails, the Attester MUST return an HTTP 400 error to the Client.¶
If the Attester accepts the request, it will look up the state stored for this Client. It will look up the count of previously generate tokens for this Client using the same Anonymous Origin ID. See Section 5.1.2 for more details.¶
If the Attester has stored state that a previous request for this Anonymous Origin ID was rejected by the Issuer in the current policy window, it SHOULD reject the request without forwarding it to the Issuer.¶
If the Attester detects this Client has changed their Client Key more frequently than allowed as described in Section 5.1.2, it SHOULD reject the request without forwarding it to the Issuer.¶
Assuming all checks in Section 5.3 succeed, the Attester generates an HTTP POST request to send to the Issuer with the Client's TokenRequest as the body. The Attester MUST NOT add information that will uniquely identify a Client, or associate the request with a small set of possible Clients. Extensions to this protocol MAY allow Attesters to add information that can be used to separate large populations, such as providing information about the country or region to which a Client belongs. An example request is shown below.¶
:method = POST :scheme = https :authority = issuer.net :path = /token-request accept = message/token-response cache-control = no-cache, no-store content-type = message/token-request content-length = <Length of TokenRequest> <Bytes containing the TokenRequest>¶
The Attester and the Issuer MUST use a secure and Issuer-authenticated HTTPS connection. Also, Issuers MUST authenticate Attesters, either via mutual TLS or another form of application-layer authentication. They MAY additionally use mechanisms such as TLS certificate pinning, to mitigate the risk of channel compromise; see Section 8 for additional about this channel.¶
Upon receipt of the forwarded request, the Issuer validates the following conditions:¶
If any of these conditions is not met, the Issuer MUST return an HTTP 400 error to the Attester, which will forward the error to the client.¶
The Issuer determines the correct Issuer Key by using the decrypted Origin Name value and TokenRequest.token_key_id. If there is no Token Key whose truncated key ID matches TokenRequest.token_key_id, the Issuer MUST return an HTTP 401 error to Attester, which will forward the error to the client. The Attester learns that the client's view of the Origin key was invalid in the process.¶
If the Issuer is willing to give a token to the Client, the Issuer decrypts TokenRequest.encrypted_origin_name to discover "origin". If this fails, the Issuer rejects the request with a 400 error. Otherwise, the Issuer validates and processes the token request with Issuer Origin Secret corresponding to the designated Origin as described in Section 7.3. If this fails, the Issuer rejects the request with a 400 error. Otherwise, the output is index_key.¶
The Issuer completes the issuance flow by computing a blinded response as follows:¶
blind_sig = rsabssa_blind_sign(skP, TokenRequest.blinded_msg)¶
skP
is the private key corresponding to Token Key, known only to the Issuer.¶
The Issuer generates an HTTP response with status code 200 whose body consists of blind_sig, with the content type set as "message/token-response", the index_key set in the "Sec-Token-Origin" header, and the limit of tokens allowed for a Client for the Origin within a policy window set in the "Sec-Token-Limit" header. This limit SHOULD NOT be unique to a specific Origin, such that the Attester could use the value to infer which Origin the Client is accessing (see Section 9).¶
:status = 200 content-type = message/token-response content-length = <Length of blind_sig> sec-token-origin = index_key sec-token-limit = Token limit <Bytes containing the blind_sig>¶
Upon receipt of a successful response from the Issuer, the Attester extracts the "Sec-Token-Origin" header, and uses the value to determine Anonymous Issuer Origin ID as described in Section 7.4.¶
If the "Sec-Token-Origin" is missing, or if the same Anonymous Issuer Origin ID was previously received in a response for a different Anonymous Origin ID within the same policy window, the Attester MUST drop the token and respond to the client with an HTTP 400 status. If there is not an error, the Anonymous Issuer Origin ID is stored alongside the state for the Anonymous Origin ID.¶
The Attester also extracts the "Sec-Token-Limit" header, and compares the limit against the previous count of accesses for this Client for the Anonymous Origin ID. If the count is greater than or equal to the limit, the Attester drops the token and responds to the client with an HTTP 429 (Too Many Requests) error.¶
For all other cases, the Attester forwards all HTTP responses unmodified to the Client as the response to the original request for this issuance.¶
When the Attester detects successful token issuance, it MUST increment the counter in its state for the number of tokens issued to the Client for the Anonymous Origin ID.¶
Upon receipt, the Client handles the response and, if successful, processes the body as follows:¶
authenticator = rsabssa_finalize(pkI, token_input, blind_sig, blind_inv)¶
If this succeeds, the Client then constructs a token as described in [AUTHSCHEME] as follows:¶
struct { uint16_t token_type = 0x0003 uint8_t nonce[32]; uint8_t context[32]; uint8_t token_key_id[Nid]; uint8_t authenticator[Nk] } Token;¶
Given a NameKey
(Origin Name Key), Clients produce encrypted_origin_name and authenticate
the contents of the TokenRequest using the following values:¶
Beyond the key configuration inputs, Clients also require the following inputs defined
in Section 5.3: token_key_id
, blinded_msg
, request_key
, and origin_name_key_id
.¶
Together, these are used to encapsulate Origin Name (origin_name
) and produce
Encrypted Origin Name (encrypted_origin
).¶
origin_name
contains the name of the origin that initiated the challenge, as
taken from the TokenChallenge.origin_info field. If the TokenChallenge.origin_info field
is empty, origin_name
is set to the empty string "".¶
The process for generating encrypted_origin
from origin_name
is as follows:¶
pad
.¶
Note that enc is of fixed-length, so there is no ambiguity in parsing this structure.¶
In pseudocode, this procedure is as follows:¶
enc, context = SetupBaseS(pkI, "TokenRequest") aad = concat(encode(1, keyID), encode(2, kemID), encode(2, kdfID), encode(2, aeadID), encode(2, token_type), encode(1, token_key_id), encode(Nk, blinded_msg), encode(49, request_key), encode(32, origin_name_key_id)) ct = context.Seal(aad, pad(origin_name)) encrypted_origin_name = concat(enc, ct)¶
Issuers reverse this procedure to recover the (padded) Origin Name by computing the AAD as described above and decrypting encrypted_origin_name with their private key skI (the private key corresponding to pkI), and stripping off padding bytes. In pseudocode, this procedure is as follows:¶
enc, ct = parse(encrypted_origin_name) aad = concat(encode(1, keyID), encode(2, kemID), encode(2, kdfID), encode(2, aeadID), encode(2, token_type), encode(1, token_key_id), encode(Nk, blinded_msg), encode(49, request_key), encode(32, origin_name_key_id)) enc, context = SetupBaseR(enc, skI, "TokenRequest") origin_name, error = context.Open(aad, ct)¶
The resulting value of origin_name is used by the Issuer to determine which rate-limit values to send to the Attester for enforcement. If the decrypted origin_name is the empty string "", the Issuer applies a cross-origin rate-limit policy, if supported. If the decrypted origin_name is the empty string "" and the Issuer does not support cross-origin rate limiting, then it MUST abort the protocol as described in Section 5.4.¶
This section describes the Client, Attester, and Issuer behavior in computing Anonymous Issuer Origin ID, the stable mapping based on client identity and origin name. At a high level, this functionality computes y = F(x, k), where x is a per-Client secret and k is a per-Origin secret, subject to the following constraints:¶
The interaction between Client, Attester, and Issuer in computing this functionality is shown below.¶
Client Attester Issuer (request, signature) ----------------------> (request, signature) ----------------------> (response) <----------------------¶
The protocol for computing this functionality is divided into sections for each of the participants. Section 7.1 describes Client behavior for initiating the computation with its per-Client secret, Section 7.2 describes Attester behavior for verifying Client requests, Section 7.3 describes Issuer behavior for computing the mapping with its per-Origin secret, and Section 7.4 describes the final Attester step for computing the client-origin index.¶
The index computation is based on ECDSA [ECDSA] instantiated with P-384 and SHA-384 and extended with key blinding support as described in [KEYBLINDING]. It uses the following functions:¶
This section describes the Client behavior for generating an one-time-use request key and signature. Clients provide their Client Secret as input to the request key generation step, and the rest of the token request inputs to the signature generation step.¶
Clients produce request_key
by masking Client Key and Client Secret with a
randomly chosen blind. Let pk_sign
and sk_sign
denote Client Key and
Client Secret, respectively. This process is done as follows:¶
pk_sign
with sk_blind
to compute a blinded public key, request_key
.¶
In pseudocode, this is as follows:¶
sk_blind = ECDSA-KeyGen() blinded_key = ECDSA-BlindPublicKey(pk_sign, sk_blind) request_key = ECDSA-SerializePublicKey(blinded_key) request_blind = ECDSA-SerializePrivatekey(sk_blind)¶
Clients produce signature of their request based on the following inputs defined in Section 5.3:
token_key_id
, blinded_msg
, request_key
, name_key_id
, encrypted_origin_name
.
This process requires the blind value sk_blind
produced during the Section 7.1.1 process.
As above, let pk and sk denote Client Key and Client Secret, respectively. Given these
values, this signature process works as follows:¶
sk_blind
over the input message using
Client Secret, sk_sign
as the signing key.¶
In pseudocode, this is as follows:¶
context = concat(0x0003, // token_type token_key_id, blinded_msg, request_key, name_key_id, encrypted_origin_name) request_signature = ECDSA-BlindKeySign(sk_sign, sk_blind, context)¶
Given a TokenRequest request containing request_key
, request_signature
, and request_blind
,
as well as Client Key pk_blind
, Attesters verify the signature as follows:¶
request_key
is a valid ECDSA public key. If this fails, abort.¶
request_blind
is a valid ECDSA private key. If this fails, abort.¶
pk_sign
by blind sk_blind
, yielding a blinded key.
If this does not match request_key
, abort.¶
request_signature
over the contents of the request, excluding the
signature itself, using request_key
. If signature verification fails, abort.¶
In pseudocode, this is as follows:¶
blind_key = ECDSA-DeserializePublicKey(request_key) sk_blind = ECDSA-DeserializePrivatekey(request_blind) pk_blind = ECDSA-BlindPublicKey(pk_sign, sk_blind) if pk_blind != blind_key: raise InvalidParameterError context = parse(request[..len(request)-96]) // this matches context computed during signing valid = ECDSA-Verify(blind_key, context, request_signature) if not valid: raise InvalidSignatureError¶
Given an Issuer Origin Secret (denoted sk_origin
) and a TokenRequest, from which
request_key
and request_signature
are parsed, Issuers verify
the request signature and compute a response as follows:¶
request_key
is a valid ECDSA public key. If this fails, abort.¶
request_signature
over the contents of the request, excluding the
signature itself, using request_key
. If signature verification fails, abort.¶
request_key
by Issuer Origin Secret, sk_origin
, yielding an index key.¶
In pseudocode, this is as follows:¶
blind_key = ECDSA-DeserializePublicKey(request_key) context = parse(request[..len(request)-96]) // this matches context computed during signing valid = ECDSA-Verify(blind_key, context, request_signature) if not valid: raise InvalidSignatureError evaluated_key = ECDSA-BlindPublicKey(request_key, sk_origin) index_key = ECDSA-SerializePublicKey(evaluated_key)¶
Given an Issuer response index_key
, Client blind sk_blind
, and Client
Key (denoted pk_sign), Attesters complete the Anonymous Issuer Origin ID computation as follows:¶
index_key
is a valid ECDSA public key. If this fails, abort.¶
index_key
using the Client blind sk_blind
, yielding the index result.¶
pk_sign
as the salt, and ASCII string "anon_issuer_origin_id" as the info string,
yielding Anonymous Issuer Origin ID.¶
In pseudocode, this is as follows:¶
evaluated_key = ECDSA-DeserializePublicKey(index_key) unblinded_key = ECDSA-UnblindPublicKey(evaluated_key, sk_blind) index_result = ECDSA-SerializePublicKey(unblinded_key) pk_encoded = ECDSA-SerializePublicKey(pk_sign) anon_issuer_origin_id = HKDF-SHA384(secret=index_result, salt=pk_encoded, info="anon_issuer_origin_id")¶
This section describes security considerations relevant to the use of this protocol.¶
An attacker that can act as an intermediate between Attester and Issuer communication can influence or disrupt the ability for the Issuer to correctly rate-limit token issuance. All communication channels use server-authenticated HTTPS. Some connections, e.g., between an Attester and an Issuer, require mutual authentication between both endpoints. Where appropriate, endpoints MAY use further enhancements such as TLS certificate pinning to mitigate the risk of channel compromise.¶
Client token requests are constructed such that an Issuer cannot distinguish between any two token requests from the same Client and two requests from different Clients. We refer to this property as issuance unlinkability. This property is achieved by the way the tokens are constructed. In particular, TokenRequest.request_key and TokenRequest.request_signature are the only value in a TokenRequest that is derived from per-Client information, i.e., the Client Secret.¶
TokenRequest.request_key is computed using a freshly generated blind for each token request. As a result, the value of TokenRequest.request_key in one token request is statistically independent from Client Key. Similarly, TokenRequest.request_signature is computed using the same freshly generated blind as TokenRequest.request_key for each token request, and the resulting signature is therefore independent from signatures produced using Client Secret. More details about this unlinkability property can be found in [KEYBLINDING].¶
This unlinkability property is only intended for requests observed by the Issuer. In contrast, the Attester is required to link requests from the same Client together for the purposes of enforcing rate limits. This Attester does this by observing the Client Key. Importantly, the Client Key is not sent to the Issuer during the issuance flow, as doing this would allow the Issuer to trivially link two requests to the same Client.¶
The token request signature is also required to be unforgeable. Informally, unforgeability means that no entity can produce a valid (message, signature) pair for any blinding key without access to the private signing key. Importantly, the means the Attester cannot forge signatures on behalf of a given Client in an attempt to learn the origin name.¶
The protocol in this document is designed such that information pertaining to issuance of a token is limited to parties that need it for completing the protocol. In particular, honest-but-curious Attesters learn only the Anonymous Issuer Origin ID as described in Section 7, any per-Client information necessary for attestation, and the target Issuer for a given token request. The Attester does not directly learn the origin name associated with a given token request, though it does learn the distribution of tokens across Client interactions. This auxiliary information could be used to infer the Origin for a given token. For example, if an Issuer has only two configured Origins, each with a different token request pattern, then the distribution of Client tokens might reveal the Origin associated with a given token.¶
Malicious or otherwise compromised Attesters can choose to not follow the protocol described in this specification, allowing, for example, Clients to bypass rate limits imposed by Origins. Moreover, malicious Attesters could reveal the per-request blind (request_blind) to Issuers, breaking the unlinkability property described in Section 8.2.¶
Honest-but-curious Issuers only learn the Attester that vouches for a particular Client's token request and the origin name associated with a token request. Issuers do not learn the Anonymous Issuer Origin ID or any per-Client information used when creating a token request.¶
Conversely, malicious Issuers that do not follow the protocol can choose to not validate the token request signature, thereby allowing others to forge token requests in an attempt to learn the origin name. Malicious Issuers can also rotate token signing keys or Issuer Origin Secret values frequently in an attempt to bypass Attester-enforced rate limits. Both of these are detectable by the Attester, though. Issuers can also lie about per-origin rate limits without detection, e.g., by increasing the limit to a value well beyond any configured limit by an Origin, or return different limits for different origins to the Attester.¶
Clients learn the output token. They do not learn the Anonymous Issuer Origin ID, though the security of the protocol does not depend on keeping this value secret from Clients. Moreover, even malicious Clients cannot tamper with per-Client state stored on the Attester for other Clients, as doing so requires knowledge of their unique Client Secret.¶
This section describes privacy considerations relevant to use of this protocol.¶
Origins SHOULD only generate token challenges based on client action, such as when a user loads a website. Clients SHOULD ignore token challenges if an Origin tries to force the client to present tokens multiple times without any new client-initiated action. Failure to do so can allow malicious origins to track clients across contexts. Specifically, an origin can abuse per-user token limits for tracking by assigning each new client a random token count and observing whether or not the client can successfully redeem that many tokens in a given context. If any token redemption fails, then the origin learns information about how many tokens that client had previously been issued.¶
By rejecting repeated or duplicative challenges within a single context, the origin only learns a single bit of information: whether or not the client had any token quota left in the given policy window.¶
Rate-limited tokens are defined in terms of a Client authenticating to an Origin, where the "origin" is used as defined in [RFC6454]. In order to limit cross-origin correlation, Clients MUST verify that the name of the origin that is providing the HTTP authentication challenge is present in the TokenChallenge.origin_info list ([AUTHSCHEME]), where the matching logic is defined for same-origin policies in [RFC6454]. Clients MAY further limit which authentication challenges they are willing to respond to, for example by only accepting challenges when the origin is a web site to which the user navigated.¶
Client activity could be linked if an Origin and Issuer collude to have unique keys targeted at specific Clients or sets of Clients.¶
To mitigate the risk of a targeted Origin Name Key, the Attester can observe and validate the token_key_id presented by the Client to the Issuer. As described in Section 5, Attesters MUST validate that the token_key_id in the Client's TokenRequest matches a known public key for the Issuer. The Attester needs to support key rotation, but ought to disallow very rapid key changes, which could indicate that an Origin is colluding with an Issuer to try to rotate the key for each new Client in order to link the client activity.¶
As stated in Section 1.2, the design of this protocol is such that Attesters cannot learn the identity of origins that Clients are accessing. The Origin Name itself is encrypted in the request between the Client and the Issuer, so the Attester cannot directly learn the value. However, in order to prevent the Attester from inferring the value, additional constraints need to be added:¶
Some deployments MAY choose to relax these requirements, such as in cases where the origins being accessed are ubiquitous or do not correspond to user-specific behavior.¶
Collusion among the different entities in the Privacy Pass architecture can result in exposure of a client's per-origin access patterns.¶
For this issuance protocol, Issuers and Attesters should be run by mutually distinct organizations to limit information sharing. A single entity running an Issuer and Attester for a single token issuance flow can view the origins being accessed by a given client. Running the Issuer and Attester in this 'single Issuer/Attester' fashion reduces the privacy promises of no one entity being able to learn Client browsing patterns. This may be desirable for a redemption flow that is limited to specific Issuers and Attesters, but should be avoided where hiding origin names from the Attester is desirable.¶
If a Attester and Origin are able to collude, they can correlate a client's identity and origin access patterns through timestamp correlation. The timing of a request to an Origin and subsequent token issuance to a Attester can reveal the Client identity (as known to the Attester) to the Origin, especially if repeated over multiple accesses.¶
Issuers SHOULD generate a new (Token Key, Issuer Origin Secret) regularly, and
SHOULD maintain old and new secrets to allow for graceful updates. The RECOMMENDED
rotation interval is two times the length of the policy window for that
information. During generation, issuers must ensure the token_key_id
(the 8-bit
prefix of SHA256(Token Key)) is different from all other token_key_id
values for that origin currently in rotation. One way to ensure this uniqueness
is via rejection sampling, where a new key is generated until its token_key_id
is
unique among all currently in rotation for the origin.¶
This document updates the "Token Type" Registry ([AUTHSCHEME]) with the following value:¶
Value | Name | Publicly Verifiable | Public Metadata | Private Metadata | Nk | Nid | Reference |
---|---|---|---|---|---|---|---|
0x0003 | Rate-Limited Blind RSA | Y | N | N | 512 | 32 | This document |
This document registers four new headers for use on the token issuance path in the "Permanent Message Header Field Names" <https://www.iana.org/assignments/message-headers>.¶
The authors of this document would like to acknowledge feedback from contributors to the Privacy Pass working group for their help in improving this document. The authors also thank Frank Denis and David Schinazi for their contributions.¶
This section includes test vectors for Origin Name encryption in Section 6 and Anonymous Origin ID computation in Section 7. Test vectors for the token request and response protocol can be found in [ISSUANCE].¶
The test vector below for the procedure in Section 6 lists the following values:¶
origin_name: 746573742e6578616d706c65 kem_id: 32 kdf_id: 1 aead_id: 1 origin_name_key_seed: ccc9e9744e7461acc6f3967757e7564b78f04ae5c9cd33f8c0c9b51ddff61f4f origin_name_key: 010020c9a7a358ab74c0664b0cffd11a5f7090108086153d77e2824 72d254991fdbe1b00010001 token_type: 3 token_key_id: 104 blinded_msg: 01a7031dbbcca4ac6acaa4650219406fd9fbceb8c264e2b3be22823a679 6571230f29ec01fe2dd633bbd4c208addfa0a6dbb149aeb4aca9070bafe849301076a40b 442469066ef78525f16a4e8cb3596ac0c548932aa421d7294d98e86187100598e9b8a968 c56d2c0277132c3efdc04b6030b9800642db686254784927cfd941f8a3a1f71893e33af4 d73f71403108c882afed2129b050663839d77bbd38f9479e7242a2d31f2fbce72259aab4 acecf7da7168347a214c6e9bb77974812b363355ac8bf4703085de54e2af85e970a3a83e 2ee3d658951167fae57ab313569dcb7551c916cb0bda9c6293843fe75849eb6a33953145 43bccb7e5146b618c637ffe8fc1bae2fddb102ce6d9a9c5eee0d267849a9bfb12e633fbf ffc80b76b017ae16f70ee6c3d841b9a4b1bf88264a2d62ae564941f943001ed95b869d2f d81e82efc399507a0ed5450be2832076a40ce1dbd1e555fb0cf360e65c4087d101d87919 9180dfcecdd0a3a2340b893fd130e5d6ece273b4a646f157b344f110973403a14a5f46d3 b2ec4457e2164ce850fc395c71d4adeab69f7ec617b6b062c1afb8da558970dfea3196e9 b2acb58dcfd23f54fc2084373cd9a78f7c84047c6f64e7464999597267e9b6a0f95bf13f 634c2a23f861fc83a09d08309ad00af4abf8cf48f1f7d286f65750f20faa1310849e8aee 44c4ec6abb1c846a16a222116ad94 request_key: 8a9ffd91064e498f0b14b4b82d65255b2859da8d349e2bb362ca7c8211e 23fcd5cd01813ef6eb4b994d532c3520e1aa89e origin_name_key_id: 590ef89ec14ebd7cd0598c7c1e045838e7c484713265c10d923d314fa1ef433f encrypted_origin_name: b9d946ee16d9114b6d42524a1fe1e5fae9768c45d4b48ce13 2e758d614911c31c3c1b73a90b63735a0c53f5b94cf176c1a00392618dac727bc287d18¶
The test vector below for the procedure in Section 7 lists the following values:¶
sk_sign: a04e2a1c1a58ed8ca09419fe937507964b640981de40c6c05c14bb547b17830 d5b18c1acf236482234f0e4ed8a6a36e5 pk_sign: 03edf9e62abec27bd25f7171dcf24aeb163fbb026381cb634a8c41058b70b74 083a704177d0aee92a9be2740fd627fa3a4 sk_origin: abc3bbe04e44502bb3549551a87f4f843b7d6ba2fb789bd82ccebe7e304f0 77f52125a4829747bd5c68a8942500d898c request_blind: 0d19ff14f8d24f7cceb377a0f54b27295e270624814293493d264d967 c541b490d7e95ff1fbb40e4f89dfdd9e25ad997 request_key: 02031027dcbe5e5607749fc6e4e0967cca89d591545c0badc640febc682 50199b87f3e60112a40425a03b9e8ac58d5bb77 index_key: 03878a9ff9e8bc58c05f8b6e71374d14f81a113f2a6828c4fa482e2d3ea50 04c0d790022a71d6437f905fb5d5daf036f52 anon_issuer_origin_id: 3edd83377d6a22f5726dac066ee1864f2c0424cd4868f994b 2e16ec270f4cc4e382a5b0730ca378797171fa2aec8222f¶