Skip to content

Security Model

This page documents C3PO's security architecture: encryption, authentication, web security, and production hardening.


End-to-End Encryption

All agent ↔ C3PO communication is encrypted with ChaCha20-Poly1305 AEAD.

Cipher Suite

Component Algorithm Key Size
Encryption ChaCha20 256-bit
Authentication Poly1305 128-bit tag
Key Derivation HKDF-SHA256 256-bit output
Nonce Random 96-bit (12 bytes)

Properties

  • Confidentiality: ChaCha20 stream cipher
  • Integrity: Poly1305 MAC — any tampering invalidates the tag
  • Authenticity: Only holders of the master key can encrypt/decrypt
  • Replay resistance: Random 96-bit nonce per message
  • Per-device isolation: HKDF derives unique keys from master_key + device_id

Key Derivation

Master Key (32 bytes, per device)
    └── HKDF-SHA256
        ├── salt: device_id (UTF-8)
        ├── info: "espilon-c2-v1"
        └── output: 32-byte encryption key

Even if two devices share the same master key (unlikely), their derived encryption keys differ because the device ID serves as the HKDF salt.


Mutual Authentication

HELLO Handshake

The connection handshake provides mutual authentication:

  1. Agent proves identity: Sends HELLO:<device_id>
  2. Server proves key possession: Encrypts a random 32-byte challenge with the device's key and sends it back
  3. Agent proves key possession: Successfully decrypts the challenge

If the device ID is unknown or the key doesn't match, the connection is dropped.

Failed Authentication

  • Unknown device ID → connection closed immediately
  • Decryption failure (wrong key) → message silently dropped
  • No error messages are returned (prevents oracle attacks)

Key Management

Master Key Storage

Location Format Purpose
ESP32 NVS Binary 32 bytes Agent-side key (factory partition, namespace crypto, key master_key)
keys.json Hex-encoded JSON Server-side key store

Key Lifecycle

deploy.py provision
    ├── Generate random 32-byte key (or use provided hex)
    ├── Write to ESP32 factory NVS partition (0x10000)
    └── Store in keys.json (device_id → hex_key)

keys.json Format

{
    "abc12345": "0a1b2c3d4e5f...64_hex_chars",
    "def67890": "fa9e8d7c6b5a...64_hex_chars"
}

Critical Asset

keys.json is the most sensitive file in the deployment. If lost:

  • You cannot communicate with provisioned devices
  • Devices must be re-provisioned (re-flashed)

Always back up keys.json after provisioning devices.

Hot-Reload

If a device connects with an ID not in memory, C3PO re-reads keys.json from disk. This allows provisioning new devices via deploy.py without restarting C3PO.


Web Security

Session Management

Feature Implementation
CSRF protection Random secrets.token_hex(32) per login page
Session fixation session.clear() before setting logged_in
Credential comparison hmac.compare_digest() (constant-time)
Session cookie Flask default (HttpOnly, server-side)

Authentication Modes

Session-based (browser):

POST /login
  username=admin
  password=secret
  csrf_token=abc123...
→ Set-Cookie: session=...

Bearer token (API):

GET /api/devices
Authorization: Bearer <MULTILAT_AUTH_TOKEN>

Dual auth (API endpoints): Accept either session cookie OR Bearer token.

Input Validation

All API inputs are validated with strict patterns:

Field Validation Limit
device_id ^[a-zA-Z0-9_-]{1,32}$ 32 chars
command ^[a-zA-Z][a-zA-Z0-9_-]{0,63}$ 64 chars
argv String array Max 10 items, 256 chars each
device_ids Array or "all" Max 100 devices
hostname ^[a-zA-Z0-9_-]{1,63}$ 63 chars
modules Known set only Boolean values only
Firmware upload .bin extension Validated MIME type

Rate Limiting

Per-IP rate limiting via Flask-Limiter (in-memory storage):

Endpoint Limit Purpose
All API endpoints 200/minute General protection
POST /login 5/minute Brute-force prevention
POST /api/commands 60/minute Command flooding
POST /api/ota/deploy 10/minute Deploy abuse
POST /api/ota/upload 5/minute Upload abuse
POST /api/ota/build/start 3/minute Build resource protection

Rate limit exceeded → HTTP 429 with JSON error body.

CORS

Configurable origin whitelist via CORS_ALLOWED_ORIGINS:

CORS_ALLOWED_ORIGINS=http://localhost:8000,http://192.168.1.100:8000
  • Origin header validated against whitelist
  • OPTIONS preflight returns 204
  • Headers: Access-Control-Allow-Origin, -Headers, -Methods, -Credentials
  • Empty whitelist = allow all origins (development only)

TCP Security

Buffer Limits

Limit Value Purpose
Max buffer size 1 MB Prevents memory exhaustion
Max concurrent connections 50 ThreadPoolExecutor limit
Idle timeout 300 seconds Closes stale connections

Frame Validation

# Strict wire format regex
^[A-Za-z0-9_-]+:[A-Za-z0-9+/=]+$
  • Only alphanumeric + -_ for device IDs
  • Only valid Base64 characters for payload
  • Line-delimited (\n) for framing
  • Invalid frames are silently dropped

Silent Failure Policy

C3PO never reveals information about failures to unauthenticated clients:

  • Wrong device ID → connection closed
  • Wrong key → message dropped (no error sent)
  • Invalid frame format → dropped
  • Decryption failure → dropped

This prevents enumeration attacks and timing oracles.


Firmware Security

OTA Updates

Feature Implementation
Transport HTTPS by default (ESP-IDF TLS)
HTTP fallback Opt-in via CONFIG_ESPILON_OTA_ALLOW_HTTP
Certificate bundle Full mbedTLS certificate bundle when HTTPS
Short URL auth Token parameter: /api/ota/fw/<file>?token=<MULTILAT_AUTH_TOKEN>

Factory NVS Partition

The master key is stored in a dedicated NVS partition:

Address: 0x10000 (64 KB)
Namespace: "crypto"
Key: "master_key"
Value: 32 bytes (binary)

This partition is separate from the application partitions and survives OTA updates (only the app partitions are overwritten).


Production Hardening Checklist

Required Changes

  • Change all default secrets in .env:
    • FLASK_SECRET_KEY — random 64+ character string
    • WEB_USERNAME / WEB_PASSWORD — strong credentials
    • CAMERA_SECRET_TOKEN — random token
    • MULTILAT_AUTH_TOKEN — random token
  • Remove ESPILON_ALLOW_DEFAULTS=1 if set
  • HTTPS reverse proxy: Put C3PO behind nginx/Caddy with TLS
  • Firewall: Restrict :2626 (C2) and :5000 (camera) to agent IPs only
  • Network segmentation: C3PO on isolated management VLAN
  • Restrict CORS origins: Set specific origins, not empty (allow-all)
  • Backup keys.json: Encrypted backup after each provisioning
  • Log monitoring: Watch for repeated auth failures
  • Docker secrets: Use Docker secrets instead of .env for sensitive values

Network Diagram (Production)

┌─────────────┐     ┌──────────────┐     ┌──────────────┐
│  Internet /  │     │  Reverse     │     │   C3PO       │
│  Agent Net   ├────►│  Proxy       ├────►│  Container   │
│              │     │  (nginx+TLS) │     │              │
│              │     │  :443        │     │  :8000 (web) │
│              ├─────┤──────────────┤────►│  :2626 (c2)  │
│  ESP32       │     │  Firewall    │     │  :5000 (udp) │
│  Agents      │     │  (iptables)  │     │              │
└─────────────┘     └──────────────┘     └──────────────┘

Threat Model

Threat Mitigation
Man-in-the-middle ChaCha20-Poly1305 AEAD on all agent traffic
Key compromise Per-device keys — compromise of one device doesn't affect others
Replay attacks Random 96-bit nonce per message
Brute-force login 5/minute rate limit on /login
API abuse Per-IP rate limiting (200/min default)
Buffer overflow 1 MB TCP buffer limit
Command injection Strict regex validation on all inputs
Session fixation Session cleared before login
CSRF Token-based protection on login form
Timing attacks Constant-time credential comparison
Device enumeration Silent failure on unknown devices
Firmware tampering HTTPS OTA with certificate pinning option

See also