Chio/Docs
LOGIN · JOIN

Secrets & Signing Keys

Choose where the kernel's signing seed lives, which issuers it trusts, and how each key set rotates without breaking verification.

Deployment chio@5f63e6e

On this page

A node that signs every receipt with a key the host protects, admits capability tokens only from issuers you list, and rotates either key set while every receipt it already signed still verifies.

The kernel signing key signs receipts and checkpoints, trusted issuer keys verify capability tokens, admin tokens open control routes, and partner keys verify a partner's evidence. Replacing a signer, removing an issuer, and revoking one capability have different effects, so name the credential before you change it.

Start
Signing key treeChoose a chain.
verifier’s pins

Every chain of trust ends at a pin

A verifier accepts a signing key from its own configuration or from a signed artifact it already trusts. Six chains cover the keys a deployment signs with:

pinned
Listed in the verifier’s own configuration: trusted issuers, approvers, receipt signers, directory issuers, an expected identity.
signs
The key signs the artifact, and the signature verifies against that key alone.
named
An accepted artifact names the next key: a capability’s subject, a directory’s passport keys, an endorsed oracle key.
In Rust · validation.rs:266
pub(crate) fn trusted_issuer_keys(&self) -> Vec<chio_core::PublicKey> {
    let mut trusted = self.config.ca_public_keys.clone();
    for authority_pk in self.capability_authority.trusted_public_keys() {
        if !trusted.contains(&authority_pk) {
            trusted.push(authority_pk);
        }
    }
    let kernel_pk = self.config.keypair.public_key();
    if !trusted.contains(&kernel_pk) {
        trusted.push(kernel_pk);
    }
    trusted
}
The keys the pinned source signs with and checks. The agent’s proof-of-possession key, the delegator’s key, and the federation’s passport and oracle keys are trusted only because an accepted artifact names them. Workload identity and runtime attestation are bound by other evidence and are left out.Assurance model

1. Give the kernel one signing key that survives restarts

The kernel reads its signing key from the seed file named by the global --authority-seed-file flag, which every subcommand accepts; no environment variable sets it. A missing file is generated and written with mode 0600 through a same-directory atomic rename. Without the flag, each boot generates a fresh signer, so the node's key changes on every restart.

Terminal · pricing-service projectbash
$ export CHIO_AUTH_TOKEN="$(openssl rand -hex 32)"
$ chio --authority-seed-file ./authority.seed \
    --receipt-db ./receipts.sqlite mcp serve-http \
    --policy ./policy.yaml --server-id pricing -- python mcp_server.py
Second terminal, same project directorybash
$ ls -l ./authority.seed
Output
-rw-------  1 chio  chio  64 Oct 11 09:14 ./authority.seed

You should see -rw-------; owner and timestamp vary. The policy and server come from the pricing-service example (chio new pricing-service --example pricing-service). Reuse an existing identity's seed. Any process that can become the Chio user can read the file, so a plaintext seed file is for development.

2. Move the seed into custody the host protects

On a headless host the seed is a systemd credential. systemd-creds encrypt seals it to the host, LoadCredentialEncrypted= decrypts it into the service's private credentials directory at start, and the unit passes that path, %d, to --authority-seed-file.

Terminalbash
$ sudo systemd-creds encrypt --name=chio-authority \
    ./authority.seed /etc/credstore.encrypted/chio-authority
$ shred -u ./authority.seed
chio-pricing.serviceini
[Service]
User=chio
LoadCredentialEncrypted=chio-authority:/etc/credstore.encrypted/chio-authority
ExecStart=/usr/local/bin/chio --authority-seed-file %d/chio-authority \
    --receipt-db /var/lib/chio/receipts.sqlite \
    mcp serve-http --policy /etc/chio/policy.yaml --server-id pricing \
    -- /usr/bin/python3 /opt/pricing-service/mcp_server.py

You should see the service keep one kernel key across restarts; step 5 reads it back from a receipt. Desktops keep the key in Keychain or Secret Service through a keychain:<service>/<account> or secret-service:<name> reference from chio key init --key; orchestrators can mount it from a secret store. A running process keeps the key it loaded, so restart it after replacing the credential.

3. Hand signing to a remote signer or an enclave

A deployment that never holds the secret half implements SigningBackend from chio-core-types::crypto. The kernel asks the backend for a public key and for signatures over bytes, and never sees the seed.

SigningBackend · embedding the kernelrust
pub trait SigningBackend: Send + Sync {
    fn algorithm(&self) -> SigningAlgorithm;
    fn public_key(&self) -> PublicKey;
    fn sign_bytes(&self, message: &[u8]) -> Result<Signature>;
}

The default Ed25519Backend wraps a local Keypair. A custom backend calls AWS KMS, Google Cloud KMS, Azure Key Vault, an HSM over PKCS#11, or any other remote signer, and sign_canonical_with_backend canonicalizes a serializable value and hands the bytes to it. Under the fips feature, P-256 and P-384 ECDSA backends route through aws-lc-rs; choose the algorithm at backend construction.

A canonical receipt body is about a kilobyte, and each checkpoint signs one Merkle root over a batch. Measure the backend's latency, quotas, and cost at your receipt rate and batch size. Hardware authenticators and enclave-bound keys are listed under Custody backends.

4. List the issuers whose tokens the kernel admits

The kernel admits a capability token only when its issuer is pinned to the issuer role in the trust file it reads at start: --trust, then CHIO_TRUST, then the project's .chio/trust.toml, then ~/.chio/trust.toml. chio key init pins the node's own authority. To admit tokens a second authority mints, pin its key by name.

Terminalbash
$ chio trust pin issuer ./issuer-2026-09.pub --name issuer-2026-09
Output
pinned     issuer-2026-09  issuer  ed25519:3dc8…255f
file       ~/.chio/trust.toml

You should see the pin and the file it went to. After a restart, that issuer's tokens are admitted and any other signer is refused with UntrustedIssuer. A pin produces no receipt, so keep the trust file under change control; chio trust pins --role issuer lists the current set.

5. Read the signing key back from a receipt

chio receipt list prints one JSON receipt per line. Reads are scoped: --tenant <id> for one tenant, or --admin-all across every tenant as an administrative read. Unscoped, the command exits 1 with CHIO-KERNEL-RECEIPT-READ-CONTEXT-MISSING and a help line naming both flags.

Terminal · one deny receipt, reflowed for widthbash
$ chio --receipt-db ./chio.db receipt list --admin-all --outcome deny
Output
{"id":"rcpt-01b3…9c40",
 "timestamp":1791820531,
 "capability_id":"cap-01ab…77d1",
 "tool_server":"pricing","tool_name":"refund",
 "action":{"parameters":{"order":"order-77"},"parameter_hash":"4413…ff8a"},
 "decision":{"verdict":"deny",
             "reason":"requested tool refund on server pricing is not in capability scope",
             "guard":"kernel"},
 "receipt_kind":"mediated_decision","boundary_class":"prevent",
 "tool_origin":"caller_executed","redaction_mode":"none",
 "content_hash":"7423…e98a",
 "policy_hash":"69e9…6b55",
 "trust_level":"mediated",
 "kernel_key":"4f1c…a2e9",
 "signature":"516e…a508"}

You should see kernel_key equal to your seed's public half, and the refused refund's reason and guard inside the signed body. Identifiers and hashes are shortened here. The line is one JSON object, wrapped here, with metadata (attribution keys, signing nonce, request id) omitted.

Filter by time window, outcome, or capability
Terminalbash
# Use the database retained by the service being audited.
$ : "${RECEIPT_DB:?Set the receipt database path}"
# List receipts in a window; --since/--until are Unix seconds.
$ chio --receipt-db "$RECEIPT_DB" receipt list --since 1719792000 --until 1722470400 --admin-all

# Narrow by decision outcome, or by the capability that authorized the call.
$ chio --receipt-db "$RECEIPT_DB" receipt list --since 1719792000 --admin-all --outcome deny
$ : "${CAPABILITY_ID:?Set the capability ID from the audit record}"
$ chio --receipt-db "$RECEIPT_DB" receipt list --capability "$CAPABILITY_ID" --admin-all

Every filter is listed under Receipt list filters.

Rotating the kernel key leaves signed receipts verifiable

rotate_authority_keypair in chio-control-plane generates a fresh keypair and atomically replaces the seed file. The operator command is in Rotate Keys & Revoke.

  • One signature per receipt. Each receipt is signed once, by the key current at signing time. Rotation changes the signer for later receipts and re-signs nothing, so no overlap window is needed.
  • A stitched checkpoint chain. Each checkpoint statement records the previous checkpoint hash and the kernel public key in use, so a verifier can rebuild the key timeline across the rotation.
  • An observable transition. The control plane advances the authority generation, stamps the rotation time, and keeps the prior key in the trusted-key history at GET /v1/authority. The partner card carries the same authority key history, so partners keep verifying receipts signed before the rotation.

Read more in Authority & Rotation: the generation counter, the seedless snapshot, and the term fence.

Issuer rotation overlaps both keys, then retires one

The trust file loads at process start, so a restart applies a change. For zero downtime, pin the new key beside the old one, roll replacement processes, and shift traffic to them.

Terminalbash
$ chio trust pin issuer ./issuer-2026-10.pub --name issuer-2026-10
# restart or reload the kernel, mint with the new key, then:
$ chio trust unpin issuer-2026-09
Output
pinned     issuer-2026-10  issuer  ed25519:9c3e…41b7
file       ~/.chio/trust.toml
unpinned   issuer-2026-09  issuer

Keep both pins until producers have moved and the last old-key token's validity window has closed, or accept that unpinning the issuer invalidates those outstanding tokens. Roll the processes again after the unpin, and verify an old-key refusal and a new-key admission.

Each admin credential belongs to the service that exposes it

chio api protect reads CHIO_SIDECAR_CONTROL_TOKEN, with CHIO_API_PROTECT_CONTROL_TOKEN as an alias. chio trust serve reads CHIO_TRUST_SERVICE_TOKEN or --service-token, and the trust control plane authorizes each route for its own principal. The MCP edge's credentials are on Admin API.

Terminalbash
$ : "${CHIO_TRUST_SERVICE_TOKEN:?Set the running trust service's token}"
$ curl --fail-with-body --silent --show-error \
    -H "Authorization: Bearer $CHIO_TRUST_SERVICE_TOKEN" \
    http://127.0.0.1:8940/v1/authority

With the standalone trust service on its default loopback address, you should see the authority's generation, current key, and trusted-key history; the response itself verifies no signature. Treat each token as a service-account credential: rotate it on a schedule, scope it per environment, and keep it out of source control. Without its token, an admin API refuses every request that requires authentication.

A lost or compromised key changes what relying parties trust

The seed file is lost

Rotate to a new keypair and re-anchor relying parties on its public key. Receipts already signed stay verifiable while you retain their public keys and trust history. A new signer can extend a retained checkpoint head; trust in that signer is established separately.

The kernel signing key is compromised

Remove the signer from admission, rotate under an independently trusted authority, and assess evidence against anchors set before the suspected compromise. A later checkpoint under the compromised key repairs no trust in forged receipts.

A trusted issuer is compromised

Unpin its key and restart; capabilities it minted stop verifying on the next request. Revoke known tokens from the compromise window in the trust-control revocation store, which also revokes single tokens from a still-trusted issuer.

A compromise is found late

The receipt log is append-only and signed. Replay retained evidence forward from the last independently trusted checkpoint and examine decisions signed in the affected window. A signed log alone cannot tell a forged receipt from an authentic one made by the same key. The replay tooling and the verdict-drift Loki dashboard are on Node Observability.

Keys, variables, and key formats

Keys a node holds

KeySigns or verifiesConfigured by
Kernel signing key
Signs receipts and checkpoint statements.
--authority-seed-file seed (via load_or_create_authority_keypair), or a SigningBackend
Trusted issuer keys
Verify capability tokens from upstream authorities.
issuer pins in the trust file (chio trust pin issuer)
Sidecar control token
Authenticates admin calls to chio api protect.
CHIO_SIDECAR_CONTROL_TOKEN, alias CHIO_API_PROTECT_CONTROL_TOKEN
Trust-service token
Authenticates the trust control plane API.
CHIO_TRUST_SERVICE_TOKEN, --service-token
Partner kernel keys
Verify a partner's checkpoints, receipts, and revocation deltas.
Pinned by adding the partner card (chio partner add), which writes receipt-kernel pins with source = "partner:halden"

Partner pinning and bilateral evidence are on Bilateral Receipts.

Environment variables

VariableRead byValue
CHIO_TRUST
The kernel and every verifier, at start
Path of the trust file; its issuer pins admit capability tokens.
CHIO_SIDECAR_CONTROL_TOKEN
chio api protect
Bearer token for admin calls.
CHIO_API_PROTECT_CONTROL_TOKEN
Adapter helpers
Alias for the sidecar control token.
CHIO_TRUST_SERVICE_TOKEN
chio trust serve
Service bearer token, distinct from the sidecar and MCP edge admin tokens.
CHIO_TEE_MODE
chio-tee
Highest-priority mode: verdict-only, shadow, enforce.
CHIO_TEE_CONFIG
chio-tee
TOML path; default /etc/chio/tee.toml.
CHIO_HOME
Every image
Persistent state root; /var/lib/chio in the shipped images.

The kernel signing key has no environment variable. These variables are read in crates/products/chio-cli/src/cli/runtime.rs and crates/trust/chio-tee/src/config.rs; the key types are in crates/core/chio-core-types/src/crypto.rs, the control-plane rotation in crates/platform/chio-control-plane/src/lib.rs, and DPoP in crates/kernel/chio-kernel/src/dpop.rs.

Tool provider secrets stay behind the secret broker in Credential custody, and each service that signs security artifacts keeps its own authority key.

Key format

The primary signing algorithm is Ed25519. Keys round-trip through hex: the seed is 32 bytes as 64 hex characters, and the public key is 32 bytes as 64 hex characters.

chio_core_types::crypto · Keypairrust
impl Keypair {
    pub fn from_seed(seed: &[u8; 32]) -> Self { ... }
    pub fn from_seed_hex(hex_str: &str) -> Result<Self> {
        // Strips optional 0x prefix; rejects anything but 32 bytes.
    }
    pub fn seed_hex(&self) -> String { ... }
    pub fn public_key(&self) -> PublicKey { ... }
}

Public keys for other algorithms carry a self-describing prefix: p256:<hex> and p384:<hex> for the P-256 and P-384 ECDSA backends under the fips feature, and hybrid:<hex> for a classical signature composed with ML-DSA-65. Plain hex means Ed25519. The prefix is part of the wire format and is set by the backend, never by hand.

Hybrid post-quantum signing

Under the pq feature, the kernel signs receipts and capability tokens with a hybrid classical plus ML-DSA-65 backend. The kernel crypto floor selects the configuration and mirrors the HushSpec field policy.crypto_floor: allow_classical (the default), allow_hybrid, or pq_required. The last two require a 32-byte ML-DSA-65 keygen seed, from which the kernel builds the hybrid backend at boot (HybridSigningConfig). Verifiers key off the hybrid: prefix, so relying parties that read the hybrid form can verify a post-quantum deployment.

Receipt list filters

chio receipt list filters by --capability, --tool-server, --tool-name, --outcome (allow, deny, cancelled, incomplete), --since and --until in Unix seconds, and --min-cost and --max-cost, and pages with --cursor and --limit. It has no free-form event-type filter. The field set is on Receipt Contract.

Custody backends

Hardware authenticators. The chio-custody-hw crate binds capability signing to a hardware or WebAuthn authenticator: it produces HybridBackend-signed capabilities, keeps a replay-resistant nonce store, and cascades revocation.

Enclaves. Generate or provision the key inside a protected environment and bind its public key to a verified quote. The relying party appraises the attestation root, the binding, and any required launch measurement. Choose a fresh, sealed, or externally provisioned identity deliberately, because the quote defines no key recovery. Confidential Node has the provisioning steps.

Orchestrator secret mounts

Project the seed from a Kubernetes Secret, the AWS Secrets Manager CSI driver, or the Azure Key Vault CSI provider. The Chio process sees an ordinary file path, and the orchestrator handles secret delivery and access auditing. Supply the complete service arguments and a configured SecretProviderClass before deploying this fragment.

Pod fragmentyaml
# Kubernetes Secret + CSI projection sketch
apiVersion: v1
kind: Pod
spec:
  containers:
    - name: chio-edge
      image: ghcr.io/backbay-labs/chio-sidecar:0.2.0
      args:
        - --authority-seed-file=/secrets/authority.seed
        - mcp
        - serve-http
      volumeMounts:
        - name: chio-keys
          mountPath: /secrets
          readOnly: true
  volumes:
    - name: chio-keys
      csi:
        driver: secrets-store.csi.k8s.io
        readOnly: true
        volumeAttributes:
          secretProviderClass: chio-keys

Next: watch the node sign