Internet-Draft | Privacy Pass Token Binding | February 2025 |
Guo, et al. | Expires 1 September 2025 | [Page] |
This document provides a token binding extension for the Privacy Pass protocol. This extension allows a Client to cryptographically bind a Privacy Pass token to its own generated one-time public key during the issuance flow, without exposing the public key to the Issuer. Later, when spending the token during the redemption flow, the Client must use the corresponding private key to generate a binding proof that the Origin needs to additionally verify except the token itself, where the proof generation can optionally support channel binding. This proof constrains the legitimate presenter of the token to be only the Client who holds the private key and further constrains the legitimate usage of the token to be only the Client's specific channel when channel binding is used in the proof generation, and thus can prevent token export and replay attacks. In addition, the token binding extension does not introduce additional cryptographic primitives, and only uses the primitives currently utilized in the issuance protocol to generate the one-time public-private keypair as well as generate and verify the binding proof.¶
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 1 September 2025.¶
Copyright (c) 2025 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.¶
Privacy Pass provides an anonymous authorization architecture based on privacy-preserving authentication mechanisms [RFC9576], and it consists of the issuance protocol and the redemption protocol. In the issuance protocol [RFC9578], a Client can interact with a token Issuer to obtain Privacy Pass tokens after completing some authentication successfully, where the tokens are generated by the Issuer using the Issuer Private Key. In the redemption protocol [RFC9577], the Client holding such a token can prove to an Origin that it is authorized by the Issuer to access the Origin's protected resources, without allowing the Origin (even with the Issuer) to link it with the issuance flow.¶
However, Privacy Pass tokens pertain to bearer tokens, and any party in possession of Privacy Pass tokens can also access to the protected resources. Therefore, attackers can take advantage of this by exporting Privacy Pass tokens from a user's machines or application connections, presenting them to the application server (the Origin), and impersonating the legitimate user. The token binding extension for the Privacy Pass protocol is to prevent such attacks by cryptographically binding a Privacy Pass token to the user agent (the Client) generated one-time public key and requiring the Client presenting the token to additionally prove possession of the corresponding private key.¶
In addition, this extension does not introduce additional cryptographic primitives, and only uses the primitives (such as Schnorr NIZKP in Section 2.2 of [RFC9497] and RSA signature in [RFC8017]) currently utilized in the issuance protocol to generate the one-time public-private keypair as well as generate and verify the binding proof. Specifically, we choose the Schnorr NIZKP to achieve token binding for both privately verifiable tokens and publicly verifiable tokens because of the following reasons: 1) the keypair of the Schnorr NIZKP can be generated from a secure seed, which is not supported by the RSA signature; 2) the keypair generation of the Schnorr NIZKP is much faster than that of the RSA signature. Because the keypair is only used one time, a more lightweight proving is to directly present the one-time private key to the Origin, which is so not a zero-knowledge proof, and its verification procedure needs to compute the corresponding one-time public key and verify the bound token.¶
At a high level, Privacy Pass with token binding extension proceeds as follows. First, a Client generates a one-time public-private keypair probably within a secure hardware module, such as a Trusted Platform Module (TPM) and a Trusted Execution Environment (TEE). Then, the client concatenates the token input and the public key to obtain a bound token input, blinds the bound token input, and generates a TokenRequest similar to Section 5.1 and Section 6.1 of [RFC9578]. After finalizing the issuance protocol successfully, the client will obtain a Privacy Pass bound token. Lastly, when spending the token, the client needs to use the private key to generate a binding proof, which is used to prove possession of the private key corresponding to the public key included in the bound token input. After receiving the token, the public key and the binding proof, the Origin not only needs to verify the token bound with the public key but also needs to verify the binding proof using the bound public key.¶
In addition, if there is a channel (e.g., TLS [RFC5246] [RFC8446], HPKE [RFC9180]) between the Client and the Origin, then a secret value exported from the channel can be used as an additional input in the Schnorr NIZK proof generation to support the property of channel binding, which guarantees that the token and the binding proof for one channel that obtained by an attacker cannot be used in another channel.¶
As a result, if an attacker attempts to export and replay a Privacy Pass bound token, it also needs to use the Client's private key that corresponds to the token's bound public key. But this is hard to do if the private key is specially protected in a secure hardware module.¶
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 Section 3 of [RFC8446].¶
This document uses terms "Origin", "Client", "Issuer", "Issuance", "Redemption", "Token challenge", "Token request", "Token response" and "Token" as defined in [RFC9576], and also uses the terms "Issuer key", "Token redemption" as defined in [RFC9577] as well as the terms "Issuer Public Key", "Issuer Private Key" as defined in [RFC9578].¶
This section describes the issuance protocol with token binding extension, which includes two types of issuance protocol: one for privately verifiable tokens and another for publicly verifiable tokens.¶
This section uses notation described in Section 4.4 of [RFC9497], including SerializeElement, DeserializeElement, and DeserializeScalar.¶
The constants Ne
and Ns
are defined in Section 4.4 of [RFC9497], and the constant Nk
is defined in Section 8.2.1 of [RFC9578].¶
The Client first generates a context as follows:¶
client_context = SetupVOPRFClient("P384-SHA384", pkI)¶
Here, pkI
denotes the Issuer Public Key defined in Section 5 of [RFC9578]. "P384-SHA384" is the identifier corresponding to the OPRF(P-384, SHA-384) ciphersuite defined in [RFC9497]. SetupVOPRFClient is defined in Section 3.2 of [RFC9497].¶
The Client then generates a random 32-byte nonce
and creates a token_input
as follows:¶
nonce = random(32) challenge_digest = SHA256(challenge) token_input = concat(0x8001, nonce, challenge_digest, token_key_id)¶
Here, the challenge
is an opaque string, and might be provided by the redemption protocol described in Section 2.1 of [RFC9577]. The 2 bytes value 0x8001
represents the token type of VOPRF(P-384, SHA-384) with Token Binding (P-384, SHA-384), where the latter ciphersuite (P-384, SHA-384) will be used for token binding. The token_key_id
is a key identifier of the Issuer Public Key pkI, which is computed as described in Section 5.5 of [RFC9578].¶
The Client generates an ephemeral public-private keypair (pkE, skE) in the group of elliptic curve P-384, where pkE
and skE
are of Ne-byte length and Ns-byte length, respectively, and the constants Ne = 49
and Ns = 48
are defined in Section 4.4 of [RFC9497]. A RECOMMENDED method for generating ephemeral keypairs is as follows:¶
seed = random(Ns) ephemeral_seed = SHA384(concat(seed, nonce)) (skE, pkE) = DeriveKeyPair(ephemeral_seed, "PrivacyPassTokenBinding")¶
Here, the long-term seed
is probably generated within a secure hardware module and can be used to protect multiple tokens (or a batch of tokens), and the DeriveKeyPair function is defined in Section 3.2.1 of [RFC9497].¶
After that, the Client creates a bound_token_input
and blinds it as follows:¶
binding_pkE = SerializeElement(pkE) bound_token_input = concat(token_input, binding_pkE) blind, blinded_element = client_context.Blind(bound_token_input) blinded_msg = SerializeElement(blinded_element)¶
Here, the Blind function is defined in Section 3.3.1 and Section 3.3.2 of [RFC9497]. If the Blind function fails, the Client aborts the protocol. The Client stores the bound_token_input
, blind
and blinded_element
values locally for use when finalizing the issuance protocol to produce a token (as described in Section 3.1.3).¶
The Client creates a TokenRequest structured as follows, where structure fields are defined in Section 5.1 of [RFC9578].¶
struct { uint16_t token_type = 0x8001; uint8_t truncated_token_key_id; uint8_t blinded_msg[Ne]; } TokenRequest;¶
The Client then generates an HTTP POST request to send to the Issuer Request URL, with the TokenRequest as the content. Please refer to Section 5.1 of [RFC9578] for an example request.¶
This section is the same as Section 5.2 of [RFC9578], except that the token type value is 0x8001
.¶
Upon receipt, the Client handles the response and, if successful, deserializes the values TokenResponse.evaluate_msg and TokenResponse.evaluate_proof, and obtains evaluated_element
and proof
as follows.¶
evaluated_element = DeserializeElement(TokenResponse.evaluate_msg) c = DeserializeScalar(TokenResponse.evaluate_proof[0]) s = DeserializeScalar(TokenResponse.evaluate_proof[1]) proof = (c, s)¶
If deserialization of either value fails, the Client aborts the protocol. Otherwise, the Client processes the response as follows:¶
authenticator = client_context.Finalize(bound_token_input, blind, evaluated_element, blinded_element, proof)¶
Here, the Finalize function is defined in Section 3.3.2 of [RFC9497]. If this succeeds, the Client creates a token as follows:¶
struct { uint16_t token_type = 0x8001; uint8_t nonce[32]; uint8_t challenge_digest[32]; uint8_t token_key_id[32]; uint8_t authenticator[Nk]; } Token;¶
If the Finalize function fails, the Client aborts the protocol; otherwise it stores the token and its corresponding one-time public key binding_pkE
.¶
This section uses the SerializeElement notation defined in Section 4.3 of [RFC9497].¶
The constant Nk
is defined in Section 8.2.2 of [RFC9578].¶
The Client first generates a random 32-byte nonce
and creates a token_input
as follows:¶
nonce = random(32) challenge_digest = SHA256(challenge) token_input = concat(0x8002, nonce, challenge_digest, token_key_id)¶
Here, the challenge
is an opaque string, and might be provided by the redemption protocol described in Section 2.1 of [RFC9577]. The 2 bytes value 0x8002
represents the token type of Blind RSA (2048-bit) with Token Binding (P-256, SHA-256), where the latter ciphersuite (P-256, SHA-256) will be used for token binding. The token_key_id
is a key identifier of the Issuer Public Key pkI, which is computed as described in Section 6.5 of [RFC9578].¶
The Client generates an ephemeral public-private keypair (pkE, skE) in the group of elliptic curve P-256, where pkE
and skE
are of Ne-byte length and Ns-byte length, respectively, and the constants Ne = 33
and Ns = 32
are defined in Section 4.3 of [RFC9497]. A RECOMMENDED method for generating ephemeral keypairs is as follows:¶
seed = random(Ns) ephemeral_seed = SHA256(concat(seed, nonce)) (skE, pkE) = DeriveKeyPair(ephemeral_seed, "PrivacyPassTokenBinding")¶
Here, the long-term seed
is probably generated within a secure hardware module and can be used to protect multiple tokens (or a batch of tokens), and the DeriveKeyPair function is defined in Section 3.2.1 of [RFC9497].¶
After that, the Client creates a bound_token_input
and blinds it as follows:¶
binding_pkE = SerializeElement(pkE) bound_token_input = concat(token_input, binding_pkE) blinded_msg, blind_inv = Blind(pkI, PrepareIdentity(bound_token_input))¶
Here, pkI
denotes the Issuer Public Key defined in Section 6 of [RFC9578]. The PrepareIdentity and Blind functions are defined in Section 4.1 and Section 4.2 of [RFC9474], respectively. If the Blind function fails, the Client aborts the protocol. The Client stores the bound_token_input
and blind_inv
values locally for use when finalizing the issuance protocol to produce a token (as described in Section 3.2.3).¶
The Client creates a TokenRequest structured as follows, where structure fields are defined in Section 6.1 of [RFC9578].¶
struct { uint16_t token_type = 0x8002; uint8_t truncated_token_key_id; uint8_t blinded_msg[Nk]; } TokenRequest;¶
The Client then generates an HTTP POST request to send to the Issuer Request URL, with the TokenRequest as the content. Please refer to Section 6.1 of [RFC9578] for an example request.¶
This section is the same as Section 6.2 of [RFC9578], except that the token type value is 0x8002
.¶
Upon receipt, the Client handles the response and, if successful, processes the content as follows:¶
authenticator = Finalize(pkI, PrepareIdentity(bound_token_input), blind_sig, blind_inv)¶
Here, the Finalize function is defined in Section 4.4 of [RFC9474]. If this succeeds, the Client creates a token as follows:¶
struct { uint16_t token_type = 0x8002; uint8_t nonce[32]; uint8_t challenge_digest[32]; uint8_t token_key_id[32]; uint8_t authenticator[Nk]; } Token;¶
If the Finalize function fails, the Client aborts the protocol; otherwise it stores the token and its corresponding one-time public key binding_pkE
.¶
This section describes the redemption protocol with token binding extension, which includes two types of redemption protocol: one for privately verifiable tokens and another for publicly verifiable tokens.¶
This section uses notation described in Section 4.4 of [RFC9497], including Generator, RandomScalar, HashToScalar, SerializeElement and DeserializeElement, and SerializeScalar and DeserializeScalar.¶
The Client first creates a proof_input
as follows:¶
proof_input = concat(token, channel_binding_type, channel_binding_secret)¶
This document defines three variants for channel binding, and the following one-byte values will be used to distinguish between types:¶
Type | Value |
---|---|
no_channel_binding | 0x00 |
tls_channel_binding | 0x01 |
hpke_channel_binding | 0x02 |
The meaning of the above types and the corresponding channel_binding_secret
are described as follows:¶
The "no_channel_binding" type means that no channel binding is used in the token binding generation, then the channel_binding_secret
is a 0-byte value.¶
The "tls_channel_binding" type means that a TLS channel binding is used in the token binding generation, then the channel_binding_secret
is a 32-byte value. The computations of this secret are defined in [RFC5705] for TLS 1.2 and in Section 7.5 of [RFC8446] for TLS 1.3, respectively, and the corresponding inputs are defined in Section 2 of [RFC9266].¶
The "hpke_channel_binding" type means that a HPKE channel binding is used in the token binding generation, then the channel_binding_secret
is a 32-byte value. This secret is generated according to Section 5.3 of [RFC9180], and it is defined as follows, where the Context.Export interface is defined in Section 5.3 of [RFC9180].¶
channel_binding_secret = Context.Export("EXPORTER-Channel-Binding", 32)¶
The Client then generates a binding_proof
as follows, which is to prove possession of the private key skE
corresponding to the previously bound public key pkE
:¶
r = RandomScalar() R = r * Generator() serialized_R = SerializeElement(R) challengeTranscript = I2OSP(len(serialized_R), 2) || serialized_R || I2OSP(len(proof_input), 2) || proof_input || "Challenge" c = HashToScalar(challengeTranscript) s = r - c * skE binding_proof = (SerializeScalar(c), SerializeScalar(s))¶
Here, the HashToScalar function is based on SHA-384, and the private key skE
MAY be recovered from the long-term seed
and the Token.nonce as follows.¶
ephemeral_seed = SHA384(concat(seed, Token.nonce)) (skE, _) = DeriveKeyPair(ephemeral_seed, "PrivacyPassTokenBinding")¶
Here, the DeriveKeyPair function defined in Section 3.2.1 of [RFC9497] is used to derive only skE
.¶
The Client creates a TokenBinding structured as follows:¶
struct { uint8_t channel_binding_type; uint8_t binding_pkE[Ne]; uint8_t binding_proof[Ns+Ns]; } TokenBinding;¶
If the "no_channel_binding" type is used, then the token binding can also be generated in another more lightweight way without Schnorr NIZK proof generation and verification. Note that the keypair (skE, pkE) is only used once for a Privacy Pass bound token, so the Client can directly present the one-time private key skE
to the Origin to prove possession of this key, and thus it is not a zero-knowledge proof. In this case, the first Ns-byte value of the binding_proof
field is set to SerializeScalar(skE)
and the second Ns-byte value of this field is set to all zero bytes, and the binding_pkE
field is set to zero-length string since the one-time public key pkE
can be recovered from the one-time private key skE
.¶
When used for Client authorization, the "PrivateToken" authentication scheme defines one parameter, "token", which contains the base64url-encoded Token structure. This document defines a new parameter, "token_binding", which contains the base64url-encoded TokenBinding structure. This document follows the default padding behavior described in Section 3.2 of [RFC4648], so the base64url value MUST include padding. The Client presents the Token structure and the TokenBinding structure to the Origin in a new HTTP request using the Authorization header field as follows:¶
Authorization: PrivateToken token="abc...", token_binding="def..."¶
Upon receipt, the Origin needs to verify both the token and the token binding, and if any one of verifications fails, this authorization request will be rejected.¶
For the token type of VOPRF(P-384, SHA-384) with Token Binding (P-384, SHA-384) (0x8001), verifying a token requires creating a VOPRF context using the Issuer Private Key, evaluating the bound token input, and comparing the result against the token authenticator value.¶
server_context = SetupVOPRFServer("P384-SHA384", skI) token_authenticator_input = concat(Token.token_type, Token.nonce, Token.challenge_digest, Token.token_key_id) bound_token_authenticator_input = concat(token_authenticator_input, TokenBinding.binding_pkE) bound_token_authenticator = server_context.Evaluate(bound_token_authenticator_input) valid = (bound_token_authenticator == Token.authenticator)¶
Here, the SetupVOPRFServer and Evaluate functions are defined in Section 3.3.1 and Section 3.2 of [RFC9497], respectively.¶
Verifying a token binding requires checking that TokenBinding.binding_proof is a valid Schnorr NIZK proof using the TokenBinding.binding_pkE, where the HashToScalar function is based on SHA-384.¶
proof_verification_input = concat(token, TokenBinding.channel_binding_type, expected_channel_binding_secret) pkE = DeserializeElement(TokenBinding.binding_pkE) c = DeserializeScalar(TokenBinding.binding_proof[0]) s = DeserializeScalar(TokenBinding.binding_proof[1]) R = (s * Generator()) + (c * pkE) serialized_R = SerializeElement(R) challengeTranscript = I2OSP(len(serialized_R), 2) || serialized_R || I2OSP(len(proof_verification_input), 2) || proof_verification_input || "Challenge" expectedC = HashToScalar(challengeTranscript) valid = (expectedC == c)¶
If the "no_channel_binding" type is used and the token binding is generated without using Schnorr NIZKP, then a token binding is implicitly verified in the above token verification, where the binding_pkE
is computed as follows.¶
skE = DeserializeScalar(TokenBinding.binding_proof[0]) pkE = skE * Generator() binding_pkE = SerializeElement(pkE)¶
This section uses notation described in Section 4.3 of [RFC9497], including Generator, RandomScalar, HashToScalar, SerializeElement and DeserializeElement, and SerializeScalar and DeserializeScalar.¶
The Client first creates a proof_input
as follows:¶
proof_input = concat(token, channel_binding_type, channel_binding_secret)¶
Here, the channel_binding_type
and the channel_binding_secret
fields are defined in Section 4.1.1.¶
The Client then generates a binding_proof
as follows, which is to prove possession of the private key skE
corresponding to the previously bound public key pkE
:¶
r = RandomScalar() R = r * Generator() serialized_R = SerializeElement(R) challengeTranscript = I2OSP(len(serialized_R), 2) || serialized_R || I2OSP(len(proof_input), 2) || proof_input || "Challenge" c = HashToScalar(challengeTranscript) s = r - c * skE binding_proof = (SerializeScalar(c), SerializeScalar(s))¶
Here, the HashToScalar function is based on SHA-256, and the private key skE
MAY be recovered from the long-term seed
and the Token.nonce as follows.¶
ephemeral_seed = SHA256(concat(seed, Token.nonce)) (skE, _) = DeriveKeyPair(ephemeral_seed, "PrivacyPassTokenBinding")¶
Here, the DeriveKeyPair function defined in Section 3.2.1 of [RFC9497] is used to derive only skE
.¶
The Client creates a TokenBinding structured as follows:¶
struct { uint8_t channel_binding_type; uint8_t binding_pkE[Ne]; uint8_t binding_proof[Ns+Ns]; } TokenBinding;¶
If the "no_channel_binding" type is used, then the token binding can also be generated by using the same lightweight way as in Section 4.1.1, thus is omitted here.¶
As an example, the Client presents the Token structure and the TokenBinding structure to the Origin in a new HTTP request using the Authorization header field as follows:¶
Authorization: PrivateToken token="abc...", token_binding="def..."¶
Upon receipt, the Origin needs to verify both the token and the token binding, and if any one of verifications fails, this authorization request will be rejected.¶
For the token type of Blind RSA (2048-bit) with Token Binding (P-256, SHA-256) (0x8002), verifying a token requires checking that Token.authenticator is a valid RSA signature over the bound token input using the Issuer Public Key.¶
token_authenticator_input = concat(Token.token_type, Token.nonce, Token.challenge_digest, Token.token_key_id) bound_token_authenticator_input = concat(token_authenticator_input, TokenBinding.binding_pkE) valid = RSASSA-PSS-VERIFY(pkI, bound_token_authenticator_input, Token.authenticator)¶
Here, the RSASSA-PSS-VERIFY function is defined in Section 8.1.2 of [RFC8017], using SHA-384 as the hash function, MGF1 with SHA-384 as the Probabilistic Signature Scheme (PSS) mask generation function (MGF), and a 48-byte salt length (sLen).¶
Verifying a token binding requires checking that TokenBinding.binding_proof is a valid Schnorr NIZK proof using the TokenBinding.binding_pkE, where the HashToScalar function is based on SHA-256.¶
proof_verification_input = concat(token, TokenBinding.channel_binding_type, expected_channel_binding_secret) pkE = DeserializeElement(TokenBinding.binding_pkE) c = DeserializeScalar(TokenBinding.binding_proof[0]) s = DeserializeScalar(TokenBinding.binding_proof[1]) R = (s * Generator()) + (c * pkE) serialized_R = SerializeElement(R) challengeTranscript = I2OSP(len(serialized_R), 2) || serialized_R || I2OSP(len(proof_verification_input), 2) || proof_verification_input || "Challenge" expectedC = HashToScalar(challengeTranscript) valid = (expectedC == c)¶
If the "no_channel_binding" type is used and the token binding is generated without using Schnorr NIZKP, then a token binding is implicitly verified in the above token verification, where the binding_pkE
is computed as follows.¶
skE = DeserializeScalar(TokenBinding.binding_proof[0]) pkE = skE * Generator() binding_pkE = SerializeElement(pkE)¶
This document defines two new token types "0x8001" and "0x8002" with the following contents, and requests that IANA add the two values to the "Privacy Pass Token Types" Registry defined in Section 6.2 of [RFC9577].¶
Note that Token Binding, Ne, Ns are newly added fields, where the latter two MUST be provided if Token Binding is Y, or not applicable (N/A) otherwise.¶
Value: 0x8001¶
Name: VOPRF(P-384, SHA-384) with Token Binding (P-384, SHA-384)¶
Token Structure: As defined in Section 3.1.¶
Token Key Encoding: Serialized using SerializeElement (Section 2.1 of [RFC9497]).¶
TokenChallenge Structure: As defined in Section 2.1 of [RFC9577].¶
Publicly Verifiable: N¶
Public Metadata: N¶
Private Metadata: N¶
Nk: 48¶
Nid: 32¶
Token Binding: Y¶
Ne: 49¶
Ns: 48¶
Change controller: IETF¶
Reference: This document, Section 3.1¶
Notes: None¶
Value: 0x8002¶
Name: Blind RSA (2048-bit) with Token Binding (P-256, SHA-256)¶
Token Structure: As defined in Section 3.2.¶
Token Key Encoding: Serialized as a DER-encoded SubjectPublicKeyInfo (SPKI) object using the RSASSA-PSS OID [RFC5756].¶
TokenChallenge Structure: As defined in Section 2.1 of [RFC9577].¶
Publicly Verifiable: Y¶
Public Metadata: N¶
Private Metadata: N¶
Nk: 256¶
Nid: 32¶
Token Binding: Y¶
Ne: 33¶
Ns: 32¶
Change controller: IETF¶
Reference: This document, Section 3.2¶
Notes: None¶