Rotate an application credential
By the end of this guide, your application authenticates to the VTA with a freshly minted credential, and the old one no longer works.
This guide re-runs the same sealed-transfer bootstrap used to provision the application in the first place, issuing a new DID and private key for the same context and role. The context, its keys, and the application’s configuration are untouched. For how that delivery works, see Sealed transfer.
A credential that has leaked stays valid until you replace it and remove the old DID from the access control list. Rotation is also the routine answer to a credential reaching the end of its planned lifetime, rather than waiting for it to fail in production.
Use this guide when:
- An application credential has been exposed, or you suspect it has.
- A credential is due for rotation under your own key-rotation policy.
- A service is being handed to a different team and should not keep the old identity.
You do not need this guide to rotate an admin DID bound to an integration such as a mediator or WebVH host; see Rotate an integration’s admin DID instead.
Prerequisites
A running VTA. See Quickstart.
Install Rust 1.95 or later on your machine (required for compiling the Personal Network Manager (PNM) CLI).
pnminstalled and connected to the VTA.cargo install pnm-cli@0.16.4 --locked --registry crates-ioAdmin access to the context the credential is scoped to.
The old credential’s DID, visible via
pnm acl list --context <context-id>.
Step 1: Generate a new bootstrap request
Run this on a trusted machine. The generated private key stays local.
pnm bootstrap request --out rotation-request.jsonStep 2: Create the new app credential
Note the SHA-256 digest printed to stdout. You need it in Step 3.
pnm auth-credential create \
--role application \
--contexts <context-id> \
--label "<your-app>-rotated" \
--recipient rotation-request.json > rotation-bundle.txtStep 3: Open the bundle
pnm bootstrap open \
--bundle rotation-bundle.txt \
--expect-digest <digest-from-step-2> \
--out credential-new.jsonStep 4: Update your secrets manager
Replace the existing credential.json secret with the contents of credential-new.json. Restart the service to load the new credential.
Delete rotation-request.json and rotation-bundle.txt after this step. They are single-use, and neither should reach source control: the bundle carries HPKE-sealed key material, and the request file is bound to the ephemeral seed that can open it.
Step 5: Revoke the old credential
Run this once the service is authenticating with the new credential, confirmed below. The old DID stays active until you remove it. If the credential was compromised, treat this as urgent rather than routine.
# Find the old DID if you do not have it recorded
pnm acl list --context <context-id>
# Revoke it
pnm acl delete <old-did>Any token issued to the old DID is rejected at its next refresh, within the access token’s session lifetime of 15 minutes.
Confirm
Test 1: the service authenticates with the new credential
Trigger a request from the service that exercises the VTA, then check the new DID holds the role and context you expect:
pnm acl get <new-did>DID: did:key:z6Mk...
Role: application
Contexts: my-appRun this before Step 5. Revoking the old credential while the new one is not yet working takes the service offline.
Test 2: the old DID is gone from the ACL
pnm acl list --context <context-id>The old DID is absent and the new one is present. A token the old DID already holds keeps working until it expires, up to 15 minutes.
Next steps
Glad to hear it! Please tell us how we can improve more.
Sorry to hear that. Please tell us how we can improve.
Thank you for sharing your feedback so we can improve your experience.