V3.1 → V3.12 consolidated and tagged for the first GA release. Wire format unchanged from 0.4.x — 4.0 peers interoperate with 0.4.x peers byte-for-byte. The version bump is semantic: audit-cycle complete, opt-in surface fully exposed, threat model refreshed for every new surface. Highlights: - All 24 @shade/* packages bumped to 4.0.0 in lockstep. - CHANGELOG 4.0.0 section is the canonical manifest of what landed. - THREAT-MODEL extended (§10 fingerprint gates, §11 WebRTC P2P, §12 Web-Worker boundary) + residual-risks table refreshed. - OpenAPI now covers all 27 routes: prekey, transfer, KT, inbox, bridge, observer, /metrics, /healthz, /ready. - MIGRATION 0.3.x → 4.0 documented + smoke-tested against shade migrate-storage on a real SQLite DB. - docs/audit/REVIEW-BUNDLE.md + SCOPE.md ready for external reviewer. - scripts/soak.ts harness for the GA-stable 2-week soak window. - All V*.md plans archived under docs/archive/ with Status: Done. - Voice/Video carved out into V5.0; 4.0 audit focuses on the frozen non-realtime stack. Tests: TS 1000/1000 + Kotlin 11/11 cross-platform vectors green. Docker: gt.zyon.no/stian/shade-prekey:4.0.0 builds and reports version 4.0.0 on /health. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
Key Transparency (V3.12)
Status: v0.4.0+ — opt-in. Server runs unchanged when KT is off. Klient ignorerer proof-felt når KT-config mangler. Trygg å rulle ut uten klient-update.
Shades prekey-server er sannhetskilde for hvilket bundle som er publisert for hver adresse. Uten Key Transparency (KT) kan en ondsinnet eller kompromittert server bytte ut et bundle uten at noen oppdager det. Med KT er hvert bundle som leveres kryptografisk forpliktet i en append-only Merkle log som tredjeparts-witnesses kan auditere.
Se også docs/V3.12-DESIGN.md for designnotat med trusselmodell og
beslutningsspor.
Hva KT garanterer
| Angrep | Detektert? |
|---|---|
Server gir Bob feil bundle for alice |
Ja — inklusjons-proof matcher ikke |
Server gir Bob og Charlie ulike bundles for alice |
Ja — witness-gossip ser to STH-er på samme tree_size |
| Server skriver om historikk for å skjule tidligere svik | Ja — konsistens-proof feiler |
| Server signerer "stale" STH for å holde et tidsvindu åpent | Ja — klient avviser STH eldre enn maxStaleMs (default 24t) |
| Førstegangs-impersonering av en helt ny adresse | Nei — KT ser bare etter at adressen er i loggen, ikke at den er "riktig" person. Bruk V3.3 (fingerprint-gate) + V3.10 (social recovery) for det. |
Operatør: skru på KT
KT er opt-in og krever:
- Et Ed25519 signing-keypair for STH-signering. Dette er operatørens nøkkel og må beskyttes som en code-signing-key.
- En persistent KTLogStore. I produksjon:
PostgresKTLogStore. I test/dev:MemoryKTLogStore. - At klienter pinner samme
logPublicKeyOOB (typisk viaShade.config-bundling i appen).
Generere signing-key
bun run scripts/generate-kt-key.ts > kt-key.json
(Eller kjør manuelt: crypto.generateEd25519KeyPair() i en Bun REPL.)
Lagre privateKey i operatørens secret-store. Distribuér publicKey
til klienter sammen med app-config.
Boot serveren med KT
import { createPrekeyServerWithKT } from '@shade/server';
import { PostgresPrekeyStore, PostgresKTLogStore } from '@shade/storage-postgres';
import { SubtleCryptoProvider } from '@shade/crypto-web';
const crypto = new SubtleCryptoProvider();
const prekeyStore = await PostgresPrekeyStore.create(process.env.DATABASE_URL!);
const ktStore = await PostgresKTLogStore.create(process.env.DATABASE_URL!);
const { app, kt } = await createPrekeyServerWithKT({
crypto,
store: prekeyStore,
keyTransparency: {
store: ktStore,
signingPrivateKey: loadFromSecret('SHADE_KT_SIGNING_PRIVATE_KEY'),
signingPublicKey: loadFromSecret('SHADE_KT_SIGNING_PUBLIC_KEY'),
heartbeatIntervalMs: 10 * 60 * 1000, // default; 0 = off
},
});
export default { port: 3900, fetch: app.fetch };
Når KT er på blir disse rutene tilgjengelig:
| Route | Hva den returnerer |
|---|---|
GET /v1/kt/log_id |
{ logId, publicKey } (begge base64) |
GET /v1/kt/sth |
Siste signed tree head |
GET /v1/kt/sth/:treeSize |
Historisk STH for et bestemt tree_size |
GET /v1/kt/consistency?from=N1&to=N2 |
Konsistens-proof N1 → N2 |
Bundle-fetch (GET /v1/keys/bundle/:address) får nå et ktProof-felt
i responsen.
Migrasjon fra ikke-KT
KT er bakoverkompatibel:
- Skru på KT-config i serveren. Restart.
- Eksisterende klienter ignorerer proof-feltene (
ktProof,ktSth). - Etter hvert som klienter oppgraderes med KT-config (
mode: 'observe'), begynner de å verifisere. - Når øko-systemet er vant til det, eskalér klienter til
'observe-strict'for å avvise prekey-server-svar uten proof.
Ved første boot scanner KT-tjenesten ikke automatisk eksisterende
prekey-store-tilstand inn i loggen. Re-registrering av eksisterende
adresser (dvs. en POST /v1/keys/register-runde fra hver klient) er
det som backfiller. For et større deployment: anbefalt at en operatør
varsler brukerne om å re-registrere innen et tidsvindue. Klienter som
ikke re-registrerer vil feile observe-strict-fetch til de får ny key
fra peer.
Klient: skru på KT
import { createShade } from '@shade/sdk';
const shade = await createShade({
prekeyServer: 'https://shade.example.com',
address: 'alice',
keyTransparency: {
mode: 'observe-strict', // eller 'observe'
logPublicKey: KT_LOG_PUBLIC_KEY_BASE64, // eller Uint8Array
maxStaleMs: 24 * 60 * 60 * 1000, // default 24t
},
});
shade.getKTWitness() returnerer LightWitness-instansen som
samler observerte STH-er. Bruk .compare(otherSth) for manuell
gossip-sjekk mot peers.
mode: 'observe'
- Verifiserer proof når serveren leverer det.
- Skipper verifisering hvis
ktProofmangler i bundle-respons. - Anbefalt under første utrulling der ikke alle klienter har re-registrert ennå.
mode: 'observe-strict'
- Krever proof på hver
200-respons. Mangler proof → kastKTVerificationError. - Krever proof på hver
404-respons også (for absence/tombstone-pinning). - Anbefalt produksjons-modus når KT-økosystemet er etablert.
Witness / auditor
@shade/key-transparency eksporterer LightWitness. Et CLI-verktøy
eller backend-job kan bruke den slik:
import { LightWitness } from '@shade/key-transparency';
import { SubtleCryptoProvider } from '@shade/crypto-web';
const crypto = new SubtleCryptoProvider();
const witness = new LightWitness({
crypto,
logPublicKey: KT_LOG_PUBLIC_KEY,
fetcher: {
async fetchLatestSTH() {
const r = await fetch('https://shade.example.com/v1/kt/sth');
return r.json();
},
async fetchConsistencyProof(from, to) {
const r = await fetch(`https://shade.example.com/v1/kt/consistency?from=${from}&to=${to}`);
return r.json();
},
},
});
// Poll periodically (e.g. every 5 minutes)
setInterval(async () => {
try {
const sth = await witness.pollOnce();
console.log(`Observed STH: tree_size=${sth.treeSize}, root=${Buffer.from(sth.rootHash).toString('hex').slice(0, 16)}`);
} catch (err) {
console.error('Witness alarm:', err);
// Send to PagerDuty / Slack / whatever
}
}, 5 * 60 * 1000);
Witness-koden detekterer:
- Stale STH — server publiserer ikke nye STH-er i tide.
- Split view — to STH-er ved samme
tree_sizemed ulik root. - Re-write — konsistens-proof feiler.
- Wrong key —
log_idmatcher ikke pinnetlogPublicKey.
Operatørkost (estimat)
For et deployment med:
- 100k registrerte adresser
- 1 identitets-rotasjon per år per bruker
- 52 replenish per år (én i uka, ikke committed til loggen — bare register/delete er)
| Ressurs | Per år | Kommentar |
|---|---|---|
| Log-rader | ~100k | bare register/delete |
| Lagring (leaves+index) | ~25 MB | base64-kodet |
| STH-rows | ~52k | én per heartbeat (10 min) |
| STH-storage | ~7 MB | |
| CPU per STH | ~1ms | Ed25519-signing er trivielt |
| Bundle-fetch overhead | <2ms | inkluderer audit-path-bygg |
Backup: behandle KT-tabellene som "kan ikke gjenopprettes" data —
shade_kt_leaves har en database-trigger som forbyr UPDATE/DELETE i
PostgreSQL-implementasjonen. Backup-strategi:
- Daglig full backup av
shade_kt_*tabellene. - WAL-shipping anbefalt (tap < 60 s i verste fall).
- Test recovery kvartalsvis. Recovery-prosedyre står under.
Recovery
Scenario 1 — STH-signing-key tapt eller kompromittert
Loggen forblir konsistent (alle gamle STH-er er allerede signert), men nye STH-er kan ikke signeres med samme key.
Steg:
- Generer ny Ed25519-keypair.
- Skriv inn et "rotation breaks here"-leaf i loggen (operasjon = 0x03
på en spesiell
__log__-adresse) — operasjonen er rent informativ, men gjør rotasjonen synlig i tree. - Re-konfigurer serveren med ny key. Restart.
- Server publiserer en ny STH; den vil ha en ny
log_id(sidenlog_id = SHA-256(publicKey)). - Klienter må eksplisitt akseptere ny key. Inntil de pinner ny
logPublicKey, vil deresLightWitnesskasteKTLogIdMismatchError. Operatør publiserer ny key OOB med "rotated from<gammel logId>"-melding signert med gammel key (siste handling før gammel key zeroizes).
Scenario 2 — KT-database korrumpert / tapt før backup
Dette er det verste utfallet. Loggen er per design ikke gjenopprettbar — å "rekonstruere" den fra prekey-store ville bryte selve invarianten KT lover.
Steg:
- Stopp serveren.
- Deklarer en "log-restart event" via offentlig kanal (status-side,
release-notes, Twitter, etc.) — inkluder timestamp, tapte tree_size
(siste backup-bare snapshot om mulig), og ny
logPublicKey. - Generer ny KT-keypair (ikke bruk gamle).
- Boot serveren tom (tom
shade_kt_*tabell). Første STH er fratree_size = 0. - Be brukerne om å re-registrere identitetene sine. Klientene vil trigge V3.3 fingerprint-gate på første re-meldings-flyt etterpå siden rotasjons-fingerprintet endres.
- Auditor-organisasjoner kan publisere "vi observerte gammel log inntil tree_size N, ny log starter på 0 fra T+0" — dette gir sluttbruker mulighet til å vurdere hvor stort hullet er.
Beskytt mot dette: WAL-shipping + off-site backup. Aldri kjør KT med kun én database-instans uten replicas.
Scenario 3 — Witness oppdager split-view
Witness kaster KTSplitViewError i LightWitness.observe() eller
KTVerificationError i transport. Dette betyr:
- Operatøren har enten (a) hatt en software-bug som signerte to ulike STH-er ved samme tree_size, eller (b) er kompromittert / ondsinnet.
Operatør-handling:
- Pause
POST /v1/keys/register,DELETE, og bundle-fetch umiddelbart (return 503). - Audit
shade_kt_sths— hvis du finner to rader med sammetree_sizemen ulikroot_hash, har serveren gjort feil. Dette er alvorlig — finn root cause før du fortsetter. - Kommuniser ut til brukerne. Forutsett at en angriper har vært inne; trigge en bredere reset (recovery scenario 2) hvis det er mistanke om tampering.
Klient-handling:
LightWitnesshar allerede holdt brukeren tilbake.- SDK-en surfacer feilen som
KTSplitViewErrortil app-koden. - App-en bør vise advarsel: "Operatørens server kan ikke verifiseres. Avstå fra sending av sensitive meldinger inntil videre."
Sikkerhets-anbefalinger
-
Kjør minst én uavhengig witness. Operatørens egen "witness" teller ikke — det må være en separat prosess på separate infrastruktur eid av en separat aktør (community-medlem, security firm, e.l.).
-
Pin
logPublicKeyi app-binær eller signert config. En man-in-the-middle som kan bytte både prekey-server og KT-key fanges ikke av KT alene. -
Loggrotasjon krever menneske-i-løkken. Ikke automatiser key-rotation for KT — den eksplisitte breaking-event er en feature.
-
maxStaleMsbør samsvare med din heartbeat. 24t default tåler en heartbeat-pause på opptil et døgn; senk til 1–4t hvis du har strenge krav til friskhet. -
observe-strictbør være standard når økosystemet er etablert. Default'observe'er en operasjonell overgangsmodus, ikke et sluttmål.
Kjente begrensninger
- Federation mellom flere prekey-servere er ikke støttet i V3.12. Hver Shade-deployment har én log eller ingen.
- Sparse Merkle tree for adresse-index brukes ikke i V3.12 — fravær-proof er foreløpig nabopar-bevis. <100 KB ved 100k adresser er akseptabelt; sparse tree blir relevant fra ~10M+ adresser.
- One-time prekey-rotasjon committes ikke til loggen. OTP er ephemerale og inkludering ville støy-fylle loggen. Dette betyr at en server som svarer med riktig identitet men feil OTP fanges ikke av KT — forsvar mot dette ligger i V3.3 fingerprint-gate (samme identitet) + sesjons-etableringens X3DH (feil OTP gir feil shared secret → første melding feiler decryption).
Tester og test-vektorer
packages/shade-key-transparency/tests/— RFC 6962-kompatibel Merkle-log + STH + index-proofs (58 tests).packages/shade-server/tests/kt.test.ts— server-integrasjon (8 tests).packages/shade-transport/tests/kt-transport.test.ts— klient- verifikasjon over HTTP (4 tests).packages/shade-transport/tests/kt-split-view-e2e.test.ts— V3.12-akseptanse split-view-deteksjon (3 tests).packages/shade-sdk/tests/kt.test.ts— SDK-config + witness wiring (3 tests).
Totalt 76 tester dedikert til KT.