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) |