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_keysits in the immutablefctryNVS 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_keylives inkeys.json. Protect this file - it is the credential for your entire fleet.
Operator-side guidance¶
- Keep
keys.jsonon 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.