BuildIdentity
Rotate Keys & Revoke
Rotate signing keys and revoke Agent Passports, capability tokens, and affected delegated capabilities.
Revocation is visible history
When to Rotate
Chio has three independent lifecycles, each with its own rotation cadence and emergency revocation path. Rotate on a schedule to limit the lifetime of a leaked key; revoke on demand after a compromise.
- Planned rotation. Operator authority keys should roll on a fixed cadence (typically every 90 days) to limit the lifetime of a leaked seed. The trust-control service exposes
rotated_aton its authority status so verifiers can confirm the latest rotation. - Reactive revocation. A compromised subject, a bad deployment, or a failed policy evaluation is reason to revoke immediately. For capability tokens this flips one row in the revocation store. For passports this transitions the lifecycle record to
Revoked. - Superseding publication. A new passport for the same subject replaces the old one by publishing a lifecycle record with
superseded_bypointing at the new passport ID. This is a lifecycle transition that relying parties honor, not a security revocation.
These actions leave persistent records. Rotation metadata lives on the authority status; revocation records live in the sqlite revocation store or the trust-control cluster; passport lifecycle records live in the passport statuses registry.
Key and credential lifecycles
Before you run any command, understand which lifecycle you are touching. Treat each lifecycle independently.
| Object | What it authorizes | Where state lives |
|---|---|---|
Operator signing key | Signs capability tokens, issues reputation credentials, authenticates trust-control writes | Authority seed file plus TrustAuthorityStatus.rotated_at |
Agent Passport | Carries portable reputation credentials bound to a did:chio subject | PassportLifecycleRecord in the passport-statuses registry |
Capability token | Authorizes a specific tool invocation under scope, budget, and TTL | RevocationRecord in SqliteRevocationStore or trust-control |
A rotation in one lifecycle does not affect another. Rotating an operator key does not revoke outstanding capabilities signed by the previous key. Revoking a passport does not revoke the capability tokens the subject already holds. Revoke each affected credential type separately.
Rotating an Operator Signing Key
An operator signing key is an Ed25519 keypair held on disk as a seed file. Chio reads it with load_or_create_authority_keypair and rolls it with rotate_authority_keypair, both exported from chio-control-plane. The rotation is atomic: a new Ed25519 keypair is generated, written to the seed path, and the new public key is returned.
pub fn rotate_authority_keypair(path: &Path) -> Result<chio_core::PublicKey, CliError> {
let keypair = Keypair::generate();
write_authority_seed_file(path, &keypair)?;
Ok(keypair.public_key())
}In production you should rotate through the trust-control service so all replicas observe the rotation. The service exposes POST /v1/authority which performs the same key rotation and returns a TrustAuthorityStatus with the new public key and a rotated_at timestamp.
CLI commands are planned
chio trust authority rotate and chio trust authority status subcommands are not yet wired through the CLI. Today, operators roll the authority seed on disk (regenerate the --authority-seed-file target atomically) or call into the trust-control POST /v1/authority and GET /v1/authority endpoints directly. The CLI wrappers shown in the emergency runbook below are on the roadmap.After rotation, confirm that the new public key is visible to each verifier. Verifiers should query the authority endpoint and pin the new key in their local policy before the old key actually stops signing new capabilities.
Rotation does not invalidate old signatures
Revoking an Agent Passport
An Agent Passport is a bundle of signed credentials. Revocation operates on the published PassportLifecycleRecord, not on the signed passport. The record moves from Active to Revoked, stamps revoked_at, and optionally carries a revoked_reason string returned by later resolutions.
pub enum PassportLifecycleState {
Active,
Stale,
Superseded,
Revoked,
NotFound,
}
pub struct PassportLifecycleRecord {
pub passport_id: String,
pub subject: String,
pub issuers: Vec<String>,
pub issuer_count: usize,
pub published_at: u64,
pub updated_at: u64,
pub status: PassportLifecycleState,
pub superseded_by: Option<String>,
pub revoked_at: Option<u64>,
pub revoked_reason: Option<String>,
pub distribution: PassportStatusDistribution,
pub valid_until: String,
}To revoke, call passport status revoke against the same registry file or trust-control service where the passport was originally published. The record must already be published: the registry refuses to transition an entry that does not exist.
# Revoke in a local lifecycle registry
chio passport status revoke \
--passport-id <passport-artifact-id> \
--passport-statuses-file passport-statuses.json \
--reason compromised
# Revoke via trust-control (all replicas observe it)
chio \
--control-url https://trust.example.com \
--control-token operator-admin-token \
passport status revoke \
--passport-id <passport-artifact-id> \
--reason key-leakOnce committed, later resolutions return a PassportLifecycleResolution with state = Revoked. If a verifier policy has requireActiveLifecycle: true, the passport stops evaluating successfully the moment that resolution is available.
Empty reason is rejected
PassportLifecycleRecord validation fails closed if you publish an entry with an empty revoked_reason. Supply a non-empty string or omit the flag entirely. Operators reviewing the audit trail later will thank you for using the reason field.Revoking a Capability Token
Capability tokens are revoked at admission. Each admission through ChioKernel consults a RevocationStore. Flipping one row stops that token from authorizing the next admission at each kernel that can read the store.
pub trait RevocationStore: Send + Sync {
/// Check if a capability ID has been revoked.
fn is_revoked(&self, capability_id: &str) -> Result<bool, RevocationStoreError>;
/// Revoke a capability. Returns true if it was newly revoked.
/// Takes &self: implementations use interior mutability.
fn revoke(&self, capability_id: &str) -> Result<bool, RevocationStoreError>;
/// Whether this store loses its revocation set on process restart.
/// The default is the safe (loud) assumption; durable stores (sqlite,
/// trust-control) override this to false.
fn is_ephemeral(&self) -> bool {
true
}
}
pub struct RevocationRecord {
pub capability_id: String,
pub revoked_at: i64,
}The CLI command is chio trust revoke. It targets either a local sqlite revocation store (via --revocation-db) or a remote trust-control service (via --control-url). Both call the same revocation operation.
# Local single-node kernel, persistent sqlite revocation store
chio \
--revocation-db revocations.sqlite3 \
trust revoke \
--capability-id cap-test-123
# Trust-control service, cluster-wide visibility
chio \
--control-url https://trust.example.com \
--control-token operator-admin-token \
trust revoke \
--capability-id cap-test-123The command prints whether the capability was newly revoked and which backend committed the write. In JSON mode you get a machine readable result suitable for automated runbooks.
chio --json \
--revocation-db revocations.sqlite3 \
trust revoke \
--capability-id cap-test-123
# {
# "capability_id": "cap-test-123",
# "revoked": true,
# "newly_revoked": true,
# "revocation_backend": "revocations.sqlite3"
# }Revocation Cascade
Revoking a capability also blocks its delegated descendants. When you revoke a root capability, any capability whose delegation_chain contains the revoked id is rejected on presentation.
/// Revoke a capability and all descendants in its delegation subtree.
///
/// When a root capability is revoked, every capability whose
/// `delegation_chain` contains the revoked ID will also be rejected
/// on presentation (the kernel checks all chain entries against the
/// revocation store).
pub fn revoke_capability(&self, capability_id: &CapabilityId) -> Result<(), KernelError> {
info!(capability_id = %capability_id, "revoking capability");
let _ = self.with_revocation_store(|store| Ok(store.revoke(capability_id)?))?;
Ok(())
}At admission time, the kernel walks the presented capability's delegation chain. For each link it asks the revocation store whether that id has been revoked. If any ancestor is revoked, admission fails with delegation chain revoked at ancestor. Inspect the capability lineage and reissue the chain from a non-revoked ancestor.
This has three practical implications:
- Revoke a delegated-capability root when its issuer is compromised. If an agent hands out ten attenuated children and one of those children misbehaves, revoke the child. If the agent itself is compromised, revoke the parent; its children will also be rejected.
- Chains with missing ancestors also fail. If a delegation chain references an ancestor that was revoked before this capability was issued, admission refuses the chain. The kernel does not allow new authority beneath a revoked parent.
- Reissuance requires a live ancestor. Reissue from a non-revoked node to restore a subtree. The
RevocationStoretrait has no unrevocation operation.
Publishing & Checking Revocation Status
Verifiers must be able to read a revocation before it affects their decisions. Chio provides two publication paths for capability revocations and one for passport lifecycle.
- Local sqlite.
SqliteRevocationStorepersistsRevocationRecordrows on disk and exposeslist_revocations_afterfor cursor-based replication. Appropriate for single-node kernels and offline proofs. - Trust-control service. Run
chio trust servewith a revocation database; nodes that point at--control-urlsees the same revocation set. The service exposes a revocation query API with cursor pagination and a revoke admin endpoint. - Passport status distribution. Passport revocations are published into the passport-statuses registry and resolved through
/v1/public/passport/statuses/resolve. Each resolution includes anupdatedAtso consumers can distinguish currentactivestate from fail-closedstalestate against the advertisedcacheTtlSecs.
To query capability revocation status from the CLI, use chio trust status. To query passport lifecycle, use chio passport status resolve.
# Capability status
chio --json \
--revocation-db revocations.sqlite3 \
trust status \
--capability-id cap-test-123
# Passport lifecycle
chio passport status resolve \
--passport-id <passport-artifact-id> \
--passport-statuses-file passport-statuses.json
# Passport lifecycle via the public holder route (read-only, no admin token)
chio passport status resolve \
--passport-id <passport-artifact-id> \
--control-url https://trust.example.comIf you start trust-control with --advertise-url, published passport records inherit https://.../v1/public/passport/statuses/resolve as the default holder resolution endpoint. Public resolution is advertised when the distribution also carries an explicit cacheTtlSecs. You can also advertise the resolve URL through the subject DID document via chio did resolve --passport-status-url ..., which emits an ChioPassportStatusService DID service entry.
Emergency Response Runbook
When a seed file leaks or an agent is confirmed compromised, stop thinking about rotation cadence; run this procedure in order. Each step is idempotent; repeat one if a partial failure leaves you uncertain.
- 1. Revoke outstanding capabilities. Identify the root capability ids the compromised key or subject controls. For each one, run
chio trust revoke --capability-id <id>against the trust-control service. The cascade handles descendants automatically: chains containing a revoked ancestor are rejected at admission. - 2. Revoke the passport. If the compromised subject carried a passport, transition its lifecycle record with
chio passport status revoke --passport-id <id> --reason compromised. Any verifier policy withrequireActiveLifecycle: truenow fails closed for that subject. - 3. Rotate the operator key. If the leaked material was the authority seed, regenerate the keypair atomically by overwriting the
--authority-seed-filetarget and restarting the trust-control service, or by callingPOST /v1/authorityon the trust-control HTTP endpoint. The new public key appears onTrustAuthorityStatusso each replica can pin it. Achio trust authority rotateCLI wrapper is planned. - 4. Republish downstream policy. Reissue signed verifier policies and reputation credentials under the new key so that
issuer_allowlistentries keep matching. Stale policies still verify against their signer, but they no longer name the current authority. - 5. Audit the receipt log. Pull receipts for the compromised subject window and reconcile what the revoked capabilities did before the revocation landed. Revocation leaves existing receipt entries unchanged and stops new admissions.
# Emergency: one agent compromised, one operator key leaked
export CHIO_CONTROL=https://trust.example.com
export CHIO_TOKEN=operator-admin-token
# 1. Kill the agent's capability subtree
chio --control-url $CHIO_CONTROL --control-token $CHIO_TOKEN \
trust revoke --capability-id cap-root-7b0f6f63
# 2. Revoke the passport
chio --control-url $CHIO_CONTROL --control-token $CHIO_TOKEN \
passport status revoke \
--passport-id passport-7b0f6f63-v3 \
--reason compromised
# 3. Rotate the authority key (CLI wrapper planned;
# until then, POST /v1/authority on trust-control)
curl -X POST "$CHIO_CONTROL/v1/authority" \
-H "Authorization: Bearer $CHIO_TOKEN"
# 4. Confirm the new authority public key
curl "$CHIO_CONTROL/v1/authority" \
-H "Authorization: Bearer $CHIO_TOKEN"Do not wait for TTL
Summary
| Action | Command | State it writes |
|---|---|---|
| Rotate authority key | POST /v1/authority (CLI wrapper planned) | TrustAuthorityStatus.rotated_at |
| Revoke a passport | chio passport status revoke | PassportLifecycleRecord.status = Revoked |
| Supersede a passport | chio passport status publish | superseded_by = <new-id> |
| Revoke a capability | chio trust revoke --capability-id ... | RevocationRecord in the revocation store |
| Check capability status | chio trust status --capability-id ... | Read-only query |
| Check passport lifecycle | chio passport status resolve | Returns PassportLifecycleResolution |
Next Steps
- Agent Passport · how passports are issued, verified, and what lifecycle states mean to a relying party
- Delegate Between Agents · how delegation chains are built, which informs how revocation cascades through them
- Capabilities · the capability-token authorization revoked by this procedure
- Trust Control Plane · how to run
chio trust serveso revocations propagate cluster-wide