PKA Endpoint Proof
Implementer reference for AID v2 Public Key for Agent endpoint proof.
PKA Endpoint Proof
Public Key for Agent (PKA) is AID's optional endpoint-proof profile. It lets a client verify that the endpoint discovered from DNS controls the Ed25519 private key matching the public key in the selected AID record.
PKA proves endpoint key possession. It does not authenticate a user, authorize an action, issue a token, prove workload identity, or describe capabilities.
For the normative protocol text, see Specification Appendix B. This page expands that appendix into an implementer checklist.
Inputs
A verifier starts with a selected aid2 record:
v=aid2;u=https://api.example.com/mcp;p=mcp;k=ebVWLo_mVPlAeLES6KmLp5AfhTrmlb7X4OORC60ElmQ
The k value is the unpadded base64url Ed25519 JWK x member. It must decode to exactly 32 bytes.
The matching JWK shape is:
{ "kty": "OKP", "crv": "Ed25519", "x": "<k>" }
Derived Key Identity
For aid2, DNS does not publish kid or i. The expected HTTP signature keyid is derived from k with RFC 7638.
Hash this exact UTF-8 JSON serialization:
{"crv":"Ed25519","kty":"OKP","x":"<k>"}
Then encode the SHA-256 digest as unpadded base64url. Do not hash the raw public key bytes directly.
For the example key above, the expected keyid is:
WWpn_pfHui9YKR4CZtQsDGMu7_Gch2zYChfSvnxgtPk
Verifier Algorithm
- Decode
kas unpadded base64url and require 32 bytes. - Derive the expected RFC 7638 JWK thumbprint from
k. - Generate a nonce with at least 32 bytes of entropy. Unpadded base64url is recommended for transport.
- Send a PKA request to the discovered
uvalue. Remove fragments, preserve query strings, and do not follow redirects. - Include
Accept-Signaturerequesting the AID profile shown below. - Include
Cache-Control: no-store. - Parse
Signature-InputandSignaturefrom the response. - Require the covered components,
tag,keyid,alg,nonce,created,expires, and responseCache-Controlrules below. - Reconstruct the RFC 9421 signature base from the received Structured Field values.
- Verify the Ed25519 signature with the public key from DNS
k.
Request And Response
Client request header:
Accept-Signature: aid-pka=("@method";req "@target-uri";req "@authority";req "@status");created;expires;keyid="<jwk-thumbprint>";alg="ed25519";nonce="<client-challenge>";tag="aid-pka-v2"
Cache-Control: no-store
Server response headers:
Signature-Input: aid-pka=("@method";req "@target-uri";req "@authority";req "@status");created=<unix>;expires=<unix>;keyid="<jwk-thumbprint>";alg="ed25519";nonce="<client-challenge>";tag="aid-pka-v2"
Signature: aid-pka=:<base64-signature>:
Cache-Control: no-store
The signature must not cover HTTP Date.
Covered Components
The base AID v2 PKA signature covers exactly:
"@method";req"@target-uri";req"@authority";req"@status"
@method, @target-uri, and @authority are request-derived and therefore use ;req. @status is response-derived and does not use ;req. The domain-binding profile below extends this base covered set with "aid-domain";req; the tag remains aid-pka-v2.
A signed non-200 status can still prove endpoint authenticity. For example, a signed 401 can prove the endpoint before the OAuth or auth.md layer continues.
URI And Authority
The request context is based on the discovered endpoint URI:
- remove the fragment;
- preserve the query string;
- lowercase the hostname;
- omit default ports;
- retain non-default ports;
- reject redirects during PKA verification.
Servers behind reverse proxies must sign the externally visible request context, not internal hop-local host, scheme, or port values.
Freshness And Caching
created and expires are mandatory.
expiresmust be greater thancreated.expires - createdmust not exceed 300 seconds.expires - createdshould be 60 seconds or less.- Verifiers may allow small clock skew.
- The received
noncemust exactly match the verifier's challenge. - PKA requests and responses use
Cache-Control: no-store.
Servers do not need nonce storage in v2 core because the verifier supplies a one-shot nonce, the response is short-lived, and the response is not cacheable.
Rejection Checklist
Reject the PKA response when any of these are true:
- the selected record is not
aid2; kis missing, padded, not base64url, or not 32 decoded bytes;- the response follows a redirect;
Signature-InputorSignatureis missing or malformed;- covered components differ from the AID profile (either the four base components, or those four plus the optional
"aid-domain";req— see Domain Binding Profile below); tagis notaid-pka-v2;keyiddiffers from the RFC 7638 thumbprint derived from DNSk;algis not semanticallyed25519;noncediffers from the challenge;createdorexpiresis missing or outside the freshness policy;- response
Cache-Control: no-storeis missing; - Ed25519 verification fails over the reconstructed signature base.
Domain Binding Profile
The base PKA profile proves endpoint key possession but does not prove the endpoint consents to serve as agent for the queried domain. The domain-binding profile (Appendix B.7) closes that gap.
AID-Domain header
When k is present in an aid2 record, the client SHOULD send:
AID-Domain: example.com
The value is the queried domain from step 1 of the discovery algorithm: A-label form, lowercased, no trailing dot, no port. This is the same value used in Section 2.3 step 1 of the spec.
Extended request shape
The tag stays aid-pka-v2; the client requests domain binding by adding "aid-domain";req to the covered set:
AID-Domain: example.com
Accept-Signature: aid-pka=("@method";req "@target-uri";req "@authority";req "aid-domain";req "@status");created;expires;keyid="<jwk-thumbprint>";alg="ed25519";nonce="<client-challenge>";tag="aid-pka-v2"
Cache-Control: no-store
The additional covered component is "aid-domain";req — the aid-domain request header, per RFC 9421 component identifier rules. Because the covered set lives in the signed @signature-params, coverage of aid-domain is authenticated; a single tag suffices for both unbound and domain-bound proofs.
Server response shape
A server that supports this profile and serves the named domain responds with the Appendix B.3 shape, except the covered set includes "aid-domain";req. A server that does not support domain binding ignores AID-Domain and responds with the base covered set (no aid-domain), which remains a valid unbound proof.
A server that supports this profile but does not serve the named domain MUST NOT produce a signature covering that AID-Domain value. It SHOULD respond 403 with no Signature-Input. A 403 refusal is a failed endpoint proof, so discovery fails for that domain.
Domain-binding verifier rules
In addition to the Appendix B.6 checklist:
- A response is domain-bound if and only if its covered set includes
"aid-domain";req, constructed from the exactAID-Domainvalue the client sent. The covered set MUST be either the four base components or those four plus"aid-domain";req(between"@authority";reqand"@status") — nothing else. - A client that did not send
AID-DomainMUST reject a response whose covered set includesaid-domain(fail-closed).
domainBound indicator
Clients that request domain binding MUST expose a boolean domainBound field in PKA state:
truewhen a valid proof covering"aid-domain";reqwas verified for the queried domain.falsewhen the verified proof omitsaid-domain(unbound).
Rejection checklist additions
Add these checks when AID-Domain was sent:
- the covered set includes
aid-domainbut"aid-domain";reqdoes not match the exactAID-Domainvalue the client sent — reject. - the covered set includes
aid-domainbut the client did not sendAID-Domain— reject. domain-binding=requireis active and the verified proof is unbound (covered set omitsaid-domain) — fail withERR_SECURITY.
See Specification Appendix B.7 for the normative text.
Canonical Test Vector
The repository ships a canonical vector at protocol/pka_vectors.json with id v2-rfc9421-response-signature. It includes the aid2 record, k, derived JWK thumbprint, nonce, request headers, response headers, signature base, and Ed25519 signature.
Use that vector before claiming independent PKA compatibility.
Legacy Aid1 Compatibility
Legacy aid1 PKA remains available only for compatibility clients during the migration window. It uses k=z... base58btc, requires i/kid, carries the challenge through AID-Challenge, and includes HTTP Date in the legacy covered set.
Do not apply those legacy rules to aid2 records.
See Also
- Identity & PKA
- Security
- Specification Appendix B
- aid-doctor CLI
- External Identity Anchors - historical background, pre-v2 wire format