Skip to content
5 changes: 5 additions & 0 deletions .changeset/plc-identity-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@getcirrus/pds": patch
---

Signing PLC operations (`com.atproto.identity.signPlcOperation`) and generating migration tokens now require your account password, the `AUTH_TOKEN`, or an OAuth grant that includes `identity:*`. App passwords, OAuth tokens without that permission, and service auth tokens are refused. Previously any signed-in app could request a migration token and have your PDS sign a change to your identity.
9 changes: 9 additions & 0 deletions .changeset/plc-rotation-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@getcirrus/pds": minor
---

Migrating a `did:plc` account with `pds identity` now gives your PDS control of your identity. Previously the PDS you migrated from kept the only rotation key, so Cirrus couldn't sign changes to your DID: it couldn't change your handle or rotate its signing key, and migrating away from Cirrus failed at the PLC directory. `pds identity` now makes your PDS's signing key a rotation key, removes the previous PDS's key, and offers to create a recovery key that ranks above it. The recovery key works with `goat` if you ever need to recover your identity without your PDS.

Accounts that were already migrated can run the new `pds rotation-keys` command to fix this. The change can be signed by your previous PDS (with your password there and an email code), by a rotation key you hold, or by your PDS once it is a rotation key. `pds status` now reports whether your PDS can update your identity, and outbound migration explains the problem instead of failing at the PLC directory.

`pds identity --token` also works now. It previously skipped the login that signing needs.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ Your signing key controls your identity. Cloudflare secrets cannot be retrieved

When you run `pds init`, you'll be prompted to back up your signing key. Store it somewhere safe – a password manager, encrypted backup, or similar.

When you migrate a `did:plc` account with `pds identity`, you'll also be offered a recovery key. It lets you take back your identity if your PDS is lost or compromised. Keep it offline, separate from your signing key.

### Key Recovery

If you've cloned to a new machine and see the "Key Recovery Required" error:
Expand All @@ -97,9 +99,9 @@ If you've cloned to a new machine and see the "Key Recovery Required" error:

**For did:plc users:**

- If you have a recovery key registered with PLC, you can rotate to a new signing key
- Without a recovery key, you'll need to start a new identity
- See the [AT Protocol PLC documentation](https://github.com/did-method-plc/did-method-plc) for recovery operations
- Your signing key is also one of your DID's rotation keys, so while your deployed PDS is still running it can update your identity. Run `pds rotation-keys` to add a recovery key if you don't have one
- If you have a recovery key, you can use it to point your DID at a new signing key. See [Rotation and Recovery Keys](./packages/pds/README.md#rotation-and-recovery-keys)
- Without a recovery key or a running PDS, you'll need to start a new identity

## Requirements

Expand Down
65 changes: 56 additions & 9 deletions packages/pds/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,19 +135,38 @@ Updates your DID document to point to your new PDS. This is the critical step th
```bash
pds identity # Update identity for production
pds identity --dev # Update identity for local dev
pds identity --token XXX # Skip email step if you have a token
pds identity --token XXX # Skip the email step if you already have a code
```

The command:

1. Resolves your current DID to find the source PDS
2. Authenticates with your source PDS (requires your password)
3. Requests an email confirmation token
4. Gets the source PDS to sign a PLC operation with the new endpoint
5. Submits the signed operation to the PLC directory
3. Chooses your [rotation keys](#rotation-and-recovery-keys): any keys you already hold, then your new PDS's key. The source PDS's key is removed. If you don't hold any keys, it offers to create a recovery key and helps you back it up
4. Shows a summary and asks you to confirm
5. Requests an email confirmation code from the source PDS
6. Gets the source PDS to sign a PLC operation with the new endpoint, signing key and rotation keys
7. Submits the signed operation to the PLC directory and checks the result

**Note:** Only `did:plc` identities are supported. `did:web` identities don't use PLC operations.

### `pds rotation-keys`

Shows your DID's [rotation keys](#rotation-and-recovery-keys) and makes sure your PDS is one of them.

```bash
pds rotation-keys # Check and update rotation keys for production
pds rotation-keys --dev # Same, for local dev
```

Accounts migrated with an earlier version of `pds identity` still list the previous PDS's key and not your PDS's, so your PDS can't change your handle or move your identity. This command fixes that, and can also add a recovery key. It sets the rotation keys to the keys you hold, then your PDS's key. The change has to be signed by a key that is already listed, so it uses:

- **Your PDS**, if it is already a rotation key
- **Your previous PDS**, which needs your password there and an email code. This only works while your old account still exists
- **A rotation key you hold**, such as a recovery key. Paste the private key when asked; it is used locally and never sent anywhere

`pds status` reports whether your PDS can update your identity.

### `pds activate`

Enables writes on the account after migration.
Expand Down Expand Up @@ -195,6 +214,8 @@ When migrating to a new PDS, the destination will ask for a confirmation token.

The token is copied to your clipboard and displayed in the terminal. After migration completes, run `pds deactivate` on this PDS.

The PDS signs the migration with its signing key, so that key must be one of your DID's [rotation keys](#rotation-and-recovery-keys). If `pds status` says your PDS can't update your identity, run `pds rotation-keys` first.

### `pds passkey`

Manage passkeys for passwordless authentication.
Expand Down Expand Up @@ -545,14 +566,15 @@ If interrupted, run `pds migrate` again to resume.
npx pds identity
```

This updates your DID document to point to your new PDS. The command:
This updates your DID document to point to your new PDS and gives your new PDS control of your identity. The command:

1. Authenticates with your source PDS (requires password)
2. Requests an email confirmation token
3. Gets the source PDS to sign a PLC operation with your new endpoint
4. Submits the signed operation to the PLC directory
2. Sets your rotation keys: keys you hold first, then your new PDS's key. If you hold none, it offers to create a [recovery key](#rotation-and-recovery-keys). Back it up when asked
3. Requests an email confirmation code
4. Gets the source PDS to sign a PLC operation with your new endpoint, signing key and rotation keys
5. Submits the signed operation to the PLC directory

You'll receive an email with a confirmation token – enter it when prompted.
You'll receive an email with a confirmation code – enter it when prompted.

### Step 4: Activate the account

Expand Down Expand Up @@ -594,6 +616,31 @@ npx pds activate # Enable writes
npx pds status # Check everything is working
```

## Rotation and Recovery Keys

A `did:plc` identity lists up to five **rotation keys**. Any change to the DID document (your PDS, your signing key, your handle) has to be signed by one of them, so whoever holds a rotation key controls the identity.

After migration your rotation keys are:

1. Your **recovery key**, if you have one
2. Your PDS's signing key

Your PDS can then change your handle, rotate its signing key, or migrate you to another PDS, without involving your previous PDS.

The order matters. For 72 hours after any change, a higher-priority key can override it by signing a competing operation. Your recovery key ranks above your PDS's key, so if your PDS is compromised and someone changes your identity, you have 72 hours to undo it. After that the change is permanent. Your recovery key can also update your DID at any time if your PDS is gone.

Keep the recovery key offline, such as in a password manager. It is a multibase private key in the format used by [goat](https://github.com/bluesky-social/goat). To use it in an emergency, for example to point your DID at a new PDS:

```bash
goat plc update --pds https://new-pds.example.com did:plc:... > op.json
goat plc sign --plc-signing-key z... op.json > signed.json
goat plc submit --did did:plc:... signed.json
```

To override a change made in the last 72 hours, build the operation on top of the last one you trust by adding `--prev <cid>` to `goat plc update`. The audit log at `https://plc.directory/did:plc:.../log/audit` lists each operation's CID. See the [PLC specification](https://github.com/did-method-plc/did-method-plc) for details.

Run `pds rotation-keys` to check your keys or add a recovery key, and `pds status` to see whether your PDS can update your identity.

## Validation

Records are validated against AT Protocol lexicon schemas before being stored. The PDS uses optimistic validation:
Expand Down
Loading
Loading