[Signierte, Merkle-verkettete Transkripte; PROV-O JSON-LD; HMAC als Standard mit PQC opt-in]

Provenienz und Compliance

Jede mutierende Operation auf einem Evidence-Datensatz erzeugt eine signierte, Merkle-verkettete Zeile in `provenance_records` in derselben Datenbanktransaktion wie der Audit-Eintrag. Partner können das vollständige Transkript als JSON oder als PROV-O JSON-LD abrufen, server-seitig oder lokal mit einem netzlosen SDK-Helper verifizieren und es als Chain of Custody für Gerichtsverfahren vorlegen. HMAC-SHA256 als Standard; ML-DSA-65 (post-quantum) optional über den bestehenden PQC-Adapter.

Provenienz und Compliance

Merkle-verkettete Provenienz

Jede Zeile in `provenance_records` trägt `parent_id` und `parent_hash`, sodass das Transkript pro `evidence_id` eine append-only Kette bildet. Der erste Datensatz eines Evidence-Objekts hat `parent_hash: null`; jede folgende Operation (`evidence.create`, `evidence.update`, `redaction.apply`, `access.read`, von Jobs ausgelöste Ereignisse usw.) hasht das kanonische JSON des vorhergehenden Datensatzes in den eigenen `parent_hash`. Das Transkript wird anschließend durch einen Merkle-Root über die geordneten Datensätze gebunden und unabhängig signiert. Ein Verifier läuft die Kette bis zum Genesis-Datensatz zurück, berechnet jeden Link neu und verwirft das Transkript, falls ein `parent_hash` nicht passt - genau das macht eine Manipulation auf Byte-Ebene end-to-end erkennbar.

Signaturalgorithmen

JSON-Kanonikalisierung folgt RFC 8785 (JCS): das Signatur-Payload pro Datensatz ist das kanonische JSON von `{tenant_id, evidence_id, parent_id, operation, actor_id, actor_kind, content_hash, parent_hash, signed_at}`. Standardalgorithmus ist HMAC-SHA256 mit einem tenant-spezifischen Geheimnis, das per HKDF aus dem Plattform-Master-Schlüssel (`ARGUS_PROVENANCE_MASTER_KEY`) über das Label `argus.provenance.tenant.{tenant_id}` abgeleitet wird. ML-DSA-65 ist über `?algorithm=ml-dsa-65` opt-in und nutzt den vorhandenen `pqc_adapter`; die öffentlichen Verify-Keys werden auf `/v1/.well-known/provenance-keys/ml-dsa-65` veröffentlicht, damit externe Auditoren und Offline-Verifier Signaturen ohne Plattformkontakt prüfen können.

Ausgabeformate

`GET /v1/evidence/{evidence_id}/provenance` akzeptiert `?format=json` (Standard) und `?format=jsonld`. Die JSON-Form ist die wire-native Darstellung, die der Verify-Endpunkt und die SDKs verwenden. Die JSON-LD-Form gibt ein PROV-O-Dokument mit `@context: https://www.w3.org/ns/prov` und Standard-Term-Mapping aus: jeder Datensatz wird auf eine `prov:Activity` abgebildet, `prov:wasGeneratedBy` verbindet die Aktivität mit ihrem Akteur (`prov:Agent`), `prov:wasDerivedFrom` koppelt den neuen Evidence-Zustand an den vorigen Zustand (`prov:Entity`), und der Merkle-Root wird als top-level `prov:Bundle` mit der bindenden Signatur als `prov:hadPrimarySource`-artige Attribution exponiert. Wählen Sie JSON-LD, wenn Sie mit W3C-PROV-Tooling, Evidence-Katalogen oder Legaltech-Plattformen interoperieren müssen, die bereits PROV-O konsumieren.

Verifikation

Zwei Verifikationspfade sind unterstützt. Server-seitig: `POST /v1/evidence/{evidence_id}/provenance/verify` mit `{transcript: <Transkript-Objekt>}` führt die Kanonikalisierung erneut aus, berechnet jede Datensatzsignatur, prüft den Merkle-Root und liefert `{ valid, broken_links, checked_records, merkle_root_verified }`. Der Endpunkt liefert nie 4xx für ein manipuliertes, aber wohlgeformtes Transkript: 400 ist für fehlerhaft formatierte Eingaben reserviert, und ein manipuliertes Transkript liefert 200 mit `valid: false` und einem gefüllten `broken_links`-Array. Lokal/Offline: die SDKs liefern `evidence.verifyProvenanceLocal(transcript)` (TypeScript) und `evidence.verify_provenance_local(transcript)` (Python). Der Offline-Helper führt dieselben Prüfungen ohne Netz-Round-Trip durch; für ML-DSA-65 liest er den öffentlichen Verify-Key aus `/v1/.well-known/provenance-keys/ml-dsa-65`. HMAC-Verifikation bleibt server-seitig, weil das tenant-spezifische Geheimnis nie veröffentlicht wird.

PROV-O-Vokabular und juristische Interoperabilität

PROV-O ist die W3C Recommendation zur Darstellung von Provenienz: wer hat was getan, wann und auf welcher früheren Grundlage. Das Mapping unserer Datensätze auf `prov:Activity`, `prov:Agent` und `prov:Entity` ermöglicht es einem gerichtlich bestellten Auditor, einer Aufsichtsbehörde oder einem Partner, ein Transkript mit Standardwerkzeugen statt einem Knogin-spezifischen Parser einzulesen. Die JSON-LD-Form behält alle Argus-spezifischen Felder (`tenant_id`, `signature_alg`, `merkle_root`, `trace_id`, `job_id`) unter dem `argus:`-Namespace neben den PROV-O-Termen, sodass der Auditor zuerst den Standardgraphen und danach die Implementierungsdetails sieht. Diese Trennung ist für die Beweiszulässigkeit relevant: der Auditor kann die Kette in der Sprache des W3C-Standards beurteilen und erst dann in die `argus:`-Felder eintauchen, wenn eine Beweisfrage plattformspezifischen Kontext erfordert.

Nutzung in Gerichtsverfahren

Das Transkript ist so entworfen, dass es einen Chain-of-Custody-Test besteht. Jeder Datensatz nennt den Akteur (`actor_id`, `actor_kind` aus `user`, `service`, `job`, `system`), die Operation, den Content-Hash der Evidence zu diesem Zeitpunkt, den vorhergehenden Datensatz, von dem er abgeleitet ist, und einen Wall-Clock-`signed_at`, der aus der Datenbanktransaktion stammt. Das Transkript trägt denselben `trace_id`, der durch die G2-Observability-Oberfläche fließt, und wo zutreffend die `job_id` aus G3 - so kann ein Ermittler einen Provenienzeintrag mit der umgebenden Systemaktivität korrelieren, die ihn erzeugt hat. Kryptographische Integrität (HMAC oder ML-DSA-65) plus Merkle-Root bedeutet, dass ein einziges geändertes Byte das Transkript bei der Verifikation invalidiert. ML-DSA-65 ist der empfohlene Algorithmus für hochgesicherte Evidence-Pakete, weil seine öffentlichen Verify-Keys das Transkript Jahre nach Fallöffnung unabhängig prüfbar machen, selbst wenn Knogin nicht mehr Verwahrer ist.

Tenant-Isolation

Jede Lese-Operation filtert auf `tenant_id` UND `organization_id` aus dem Bearer-Token. Cross-Tenant-Zugriffe liefern 404 (nie 403), sodass die Existenz einer Provenienzkette in einem anderen Tenant nie offengelegt wird. Der SecrecyLevel der zugrunde liegenden Evidence gilt: ein Operator unterhalb des Geheimhaltungsgrades der Evidence kann deren Provenienz auch innerhalb seines eigenen Tenants nicht lesen. Das HMAC-Geheimnis ist tenant-spezifisch (HKDF-abgeleitet aus dem Plattform-Master-Key über das Label `argus.provenance.tenant.{tenant_id}`), sodass ein für einen Tenant signiertes Transkript nicht gegen einen anderen revalidiert werden kann. Beide Endpunkte schreiben einen `provenance.read`-Audit-Eintrag mit `resource_id = evidence_id` und `metadata = { record_count, format, algorithm, valid }`, sodass das Abrufen oder Verifizieren eines Transkripts selbst im Audit-Trail erfasst wird.

Unterstützte Algorithmen

HMAC-SHA256 (Standard, tenant-scoped) und ML-DSA-65 (post-quantum, FIPS 204; opt-in via ?algorithm=ml-dsa-65).

JSON-Transkript abrufen

# Fetch a JSON transcript (default algorithm: HMAC-SHA256)
curl "https://api.knogin.com/v1/evidence/ev_abc123/provenance" \
  -H "Authorization: Bearer $TOKEN"

# 200 OK
# {
#   "evidence_id": "ev_abc123",
#   "tenant_id": "tnt_123",
#   "records": [
#     {
#       "id": "01HFXY...",
#       "operation": "evidence.create",
#       "actor_id": "user_42",
#       "actor_kind": "user",
#       "content_hash": "sha256:b94d...",
#       "parent_hash": null,
#       "signature": "<hex>",
#       "signature_alg": "hmac-sha256",
#       "signed_at": "2026-05-08T10:00:00Z",
#       "trace_id": "0af7651916cd43dd8448eb211c80319c",
#       "job_id": null
#     }
#   ],
#   "merkle_root": "sha256:...",
#   "root_signature": "<hex>",
#   "root_signature_alg": "hmac-sha256",
#   "format": "json",
#   "version": "2026.4"
# }

# Same transcript, post-quantum signatures:
curl "https://api.knogin.com/v1/evidence/ev_abc123/provenance?algorithm=ml-dsa-65" \
  -H "Authorization: Bearer $TOKEN"

PROV-O JSON-LD-Transkript abrufen

# Fetch the same transcript as PROV-O JSON-LD
curl "https://api.knogin.com/v1/evidence/ev_abc123/provenance?format=jsonld" \
  -H "Authorization: Bearer $TOKEN"

# 200 OK
# {
#   "@context": [
#     "https://www.w3.org/ns/prov",
#     { "argus": "https://schema.knogin.com/argus/v1#" }
#   ],
#   "@type": "prov:Bundle",
#   "@id": "argus:evidence/ev_abc123/provenance",
#   "argus:tenant_id": "tnt_123",
#   "argus:merkle_root": "sha256:...",
#   "argus:root_signature": "<hex>",
#   "argus:root_signature_alg": "hmac-sha256",
#   "@graph": [
#     {
#       "@id": "argus:provenance/01HFXY",
#       "@type": "prov:Activity",
#       "prov:startedAtTime": "2026-05-08T10:00:00Z",
#       "argus:operation": "evidence.create",
#       "argus:content_hash": "sha256:b94d...",
#       "argus:signature": "<hex>",
#       "argus:signature_alg": "hmac-sha256",
#       "argus:trace_id": "0af7651916cd43dd8448eb211c80319c",
#       "prov:wasGeneratedBy": { "@id": "argus:agent/user_42", "@type": "prov:Agent" },
#       "prov:wasDerivedFrom": null
#     }
#   ]
# }

Transkript server-seitig verifizieren

# Server-side verification of a transcript fetched earlier.
curl -X POST https://api.knogin.com/v1/evidence/ev_abc123/provenance/verify \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"transcript\": $(cat transcript.json)}"

# 200 OK (intact)
# {
#   "valid": true,
#   "evidence_id": "ev_abc123",
#   "broken_links": [],
#   "checked_records": 5,
#   "merkle_root_verified": true
# }

# 200 OK (tampered: a single byte changed in record 01HFXY)
# {
#   "valid": false,
#   "evidence_id": "ev_abc123",
#   "broken_links": [
#     { "record_id": "01HFXY...", "reason": "content_hash_mismatch" }
#   ],
#   "checked_records": 5,
#   "merkle_root_verified": false
# }

Offline-Verifikation (TypeScript-SDK)

// Offline verification with @argus/client (TypeScript).
// HMAC verification stays server-side; ML-DSA-65 transcripts can be verified
// locally using the public verify keys from /v1/.well-known/provenance-keys.

import { ArgusClient } from "@argus/client";

const argus = new ArgusClient({
  baseUrl: "https://api.knogin.com",
  token: process.env.ARGUS_TOKEN!,
});

const transcript = await argus.evidence.getProvenance("ev_abc123", {
  format: "json",
  algorithm: "ml-dsa-65",
});

// No network round-trip; the helper fetches /v1/.well-known/provenance-keys
// once per kid, caches the result, and verifies signatures + Merkle root.
const result = await argus.evidence.verifyProvenanceLocal(transcript);

if (!result.valid) {
  console.error("Tampered transcript", result.brokenLinks);
  process.exit(1);
}
console.log("Verified", result.checkedRecords, "records");

Offline-Verifikation (Python-SDK)

# Offline verification with argus-client (Python).
# Same shape as the TypeScript helper; suitable for evidence packages
# stored in court archives long after the case opens.

from argus_client import ArgusClient

argus = ArgusClient(base_url="https://api.knogin.com", token=os.environ["ARGUS_TOKEN"])

transcript = argus.evidence.get_provenance(
    "ev_abc123",
    format="json",
    algorithm="ml-dsa-65",
)

result = argus.evidence.verify_provenance_local(transcript)

if not result.valid:
    raise SystemExit(f"Tampered transcript: {result.broken_links}")

print(f"Verified {result.checked_records} records; merkle_root_verified={result.merkle_root_verified}")

Provenienz für einen regulierten Workflow benötigt?

Öffnen Sie die API-Referenz für den öffentlichen Vertrag oder sprechen Sie mit Knogin, wenn Sie ML-DSA-65-Schlüssel für einen externen Auditor bereitgestellt haben möchten.