Platform Authentication (PP Auth)
An eVault has never been able to tell one platform from another. POST /platforms/certification mints a year-long token for any name a caller types in, and any registry-signed token bypasses access control outright. So "which platform is this?" has, until now, been answered by whoever asked.
PP Auth replaces that with a chain of trust the caller has to actually hold the keys for. A deployment proves, from scratch on every handshake, which release it is running and what the Post Platforms Association certified that release to do.
What a deployment proves
Six links, each failing closed. A verifier checks all six and reports all six — an operator debugging a rejected handshake needs the whole trace, not the first problem.
| Link | What it establishes |
|---|---|
| Possession | The caller signed a fresh challenge with the deployment key. Without this the rest is public paperwork anyone could replay. |
| Deployment authorised | A named person's wallet signed that key for this platform and environment. Authority traces to a human, not a config file. |
| Bundle integrity | Both binding documents hash to the values that signature covered, so neither can be swapped independently of the other. |
| Version identity | The version eName is derivable from the platform eName and version by UUIDv5. Arithmetic, not a lookup — nothing to spoof and no network call. |
| Release authorship | The release's submission proof re-verifies against its registry key-binding certificate. The same proof the association reviewed, checked again rather than taken on trust. |
| Accreditation | The certificate was issued (iss) by a trusted certifying authority, verifies against the JWKS that authority publishes at its own /.well-known/jwks.json, names this platform as sub and this exact version, and grants a level and a set of domains. |
If every link holds, the verifier returns a claim: the platform, the deployment, the version, the certification level, and the domains — intersected with what the release actually asked for, so a certificate naming more than the submission requested cannot widen it.
Trusted certifying authorities
Only certificates from a recognised Post Platforms Association count. By default that is https://ppa.w3ds.metastate.foundation alone; a verifier can set its own list with trustedPpaIssuers. The issuer is checked before any key is fetched, and keys always come from the trusted issuer's own JWKS. The issuerJwksUri in the evidence is never used for verification, because the deployment being checked supplies it, and a deployment could otherwise point the verifier at a key set it controls and certify itself.
The handshake
deployment verifier
| POST /pp-auth/challenge |
|------------------------------------------>|
| { nonce, audience, issuedAt, expiresAt } |
|<------------------------------------------|
| sign the canonical challenge payload |
| POST /pp-auth/verify |
| { challenge, evidence, signature } |
|------------------------------------------>|
| verify six links |
| { ok, links[], claim } |
|<------------------------------------------|
A challenge is single-use and short-lived. It is spent the moment it is answered — whether or not the chain holds — so a captured response cannot be replayed even inside its window.
The deployment presents its evidence rather than being looked up. That matters: a verifier needs only public endpoints to check it, and never needs read access to the platform's eVault, which is the access the deployment is trying to obtain in the first place.
Canonical payloads
Three codebases produce these signatures — the eID wallet, GitW3 in Go, and the registry — so the byte-for-byte forms are fixed.
| Signed thing | Payload |
|---|---|
| Handshake challenge | w3ds:pp-auth:v1: + base64url(sha256(canonical challenge)) |
| Deployment attestation bundle | gitw3:deployment:v1: + base64url(sha256(signedPayload)) |
| Release submission | gitw3:ppa:v1: + base64url(sha256(JSON.stringify(statement))) |
| Owner access policy | w3ds:access-policy:v1: + base64url(sha256(canonical statement)) |
"Canonical" means keys sorted at every depth, matching getCanonicalBindingDocumentString in evault-core and the Go implementation in GitW3. The bundle is the exception: its digest is over the signedPayload string exactly as stored, not over a re-serialisation of it.
Signatures are accepted as base64url, base58 multibase (z…), raw r‖s, or DER-wrapped. Public keys are accepted as multibase, 0x-hex or bare base64. Being strict about the bytes and liberal about how they were written is deliberate: a verifier that insists on one encoding rejects legitimate evidence.
What certification is not
The association's certificate is a trust statement, not a permission. It says what a release was found to be. The eVault stays sovereign and decides for itself what that is worth — see Access Policy.
Two independent gates, both of which must open:
- The certificate. Is this domain in what the association granted, and in what the release asked for? A social platform certified for
socialandcommunicationhas no path tofinancedata. Not because the eVault recognises it as a social platform, but becausefinanceis not in its certificate and nothing it can present puts it there. - The owner's policy. Is the level high enough, is the reputation acceptable, is this domain one the owner permits at all?
An owner's policy can only narrow a certificate, never widen it.
Backwards compatibility
Existing registry-minted platform tokens keep working. The registry stops minting new ones; deployments issued through GitW3 come with the evidence PP Auth needs. The two coexist while platforms migrate.
Where the code is
@metastate-foundation/auth/platform — both halves in one package.
verifyDeploymentChain,verifyHandshake,createChallengeStore— the verifieranswerChallenge,authenticate— the deployment sideauthorize,permittedDomains— the two gatesaccessPolicyPayload,verifyAccessPolicy— the owner's terms@metastate-foundation/auth/platform/scenario— mints a self-consistent chain from local keys, for tests and demonstrations. Never configure a production verifier with roots from it.