Skip to content

C3PO Security

C3PO holds the keys to your fleet. This page covers the keystore, per-device key isolation, and the operator-side security model. For the end-to-end framework model, see the Security section.

The keystore

Device keys live in keys.json (c3po/c2/keystore.py:KeyStore). Each entry maps a device id to its 32-byte master_key plus metadata:

{
  "esp32-home-01": {
    "key": "a1b2c3d4e5f6...64 hex chars",
    "transport": "wifi",
    "board": "esp32c6"
  },
  "legacy-device": "deadbeef...64 hex chars"
}

Two formats are accepted: the structured object (preferred, with key/transport/board) and a legacy plain hex string (read-only). New entries are always written in the structured form. The keystore is created automatically on first provisioning and can be managed from the Keystore view (add, edit, delete, generate a random key, copy to clipboard, reload from disk).

Per-device key isolation

Every device has a unique master_key. From it, both sides derive purpose-specific keys with HKDF-SHA256:

flowchart TD
    MK["master_key (per device)"] --> T["transport key<br/>ChaCha20-Poly1305"]
    MK --> S["signing key<br/>HMAC-SHA256 modules"]
    MK --> C["config key<br/>encrypted NVS"]

When C3PO receives a connection it tries each registered key during the handshake to identify the device, then uses that device's derived context for the session. Because keys are per device:

  • A module signed for one device will not load on another (signatures are key-bound).
  • A leaked single device key compromises only that device, not the fleet.
  • The Management Protocol server can authenticate every device without any shared secret.

What protects the master key

  • On the device: the master_key sits in the immutable fctry NVS partition, is read once at boot to derive the three keys, and is then zeroized from RAM. With flash encryption enabled (device key in eFuses) it is also protected against a physical flash dump.
  • On the operator host: the master_key lives in keys.json. Protect this file - it is the credential for your entire fleet.

Operator-side guidance

  • Keep keys.json on an encrypted disk with restrictive permissions; back it up securely.
  • Bind the Management Protocol server to a controlled interface and firewall port 2626; expose a public address only when a remote (e.g. GPRS) agent requires it.
  • Rotate a device's key by re-provisioning it.
  • Treat module sources as trusted code: once loaded, a module has full hardware access on the agent.

See also