← Developers

05.2 — Ingest Firmata & Evidenza

Spec riferita a api v1

L’Invariante

La firma copre i byte sul wire, non il dato. X-Edge-Signature: <alg>.<b64> è calcolata sul body HTTP esatto (req.rawBody) — un JSON riserializzato non verificherebbe. Integrità e attribuzione sono proprietà del canale, non di un payload ricostruito. Algoritmi accettati lato server: ed25519 (chiavi software, emesse al claim) ed es256 (ECDSA P-256/SHA-256, per firmatari TPM-bound).

Il Contratto

Superficie Meccanismo Fail mode
POST /sync/ingest x-edge-signature sul body grezzo 403 EDGE_SIGNATURE_REQUIRED / EDGE_SIGNATURE_INVALID
POST /sync/edge-audit idem, sulla stringa NDJSON idem
POST /bootstrap/claim il body porta node_pubkey (SPKI DER b64) + hw_meta opzionale chiave invalida → 400

I nodi con pubkey registrata sono fail-closed: senza firma → EDGE_SIGNATURE_REQUIRED, chiave sbagliata → EDGE_SIGNATURE_INVALID. I nodi senza (claim pre-rollout, provisioning via env) sono accettati — la whitelist del soft-rollout, dichiarata nel threat model. Il replay è innocuo: cp_event_dedup rende la ritrasmissione un no-op, quindi le firme non portano timestamp.

I batch di ingest firmati sono conservati verbatim in cp_ingest_batches: raw_body (bytea) + signature + node_id + event_count. Ogni evento porta batch_id verso il suo envelope firmato — l’oracolo ri-verifica i byte originali.

Il Flusso

edge:  claim → keypair sul device → privkey in edge-credentials.json (0600)
sync:  byte del body → firma → X-Edge-Signature → il CP verifica vs pubkey registrata
store: batch firmato → cp_ingest_batches (raw_body + sig) → events.batch_id
audit: GET /public/verify/event/:id → firma valida + membership + record integro

Esempio di Payload

curl -X POST $CP/api/v1/sync/ingest \
  -H "Authorization: Bearer $EDGE_API_KEY" \
  -H "x-edge-node: $NODE_ID" \
  -H "x-edge-signature: ed25519.$(sign_raw_body)" \
  -H 'content-type: application/json' \
  --data-binary @batch.json        # byte esatti firmati — niente riserializzazione

I Fallimenti Noti

Threat model dichiarato — cosa copre e cosa non copre ogni strato:

Strato / sintomo Protegge da / causa → fix
Privkey a 0600 sul device Compromissione remota/software — non sottrazione fisica di SD/SSD
Ed25519 su rawBody Manomissione in transito, firma di nodo errato — non replay (assorbito dalla dedup, by design)
cp_ingest_batches Alterazione post-ingest del payload di eventi firmati — non cancellazione della riga (l’oracolo risponde found:false) né eventi legacy non firmati
EDGE_SIGNATURE_INVALID Firma calcolata su JSON riserializzato → firma i byte letterali del body
Outbox ax_cdc_outbox Blackout di rete — le scritture restano committate in locale e drenano alla riconnessione; non cifratura at-rest (proteggi il disco dell’host separatamente)