Backend-Infrastruktur-Leitfaden (Infrastructure Guide)
Was Ihr Backend zur Unterstützung des Trusteddit-Registrierungsablaufs benötigt
Überblick (Overview)
Der Trusteddit-Registrierungsablauf erfordert zwei Kernaufgaben im Backend: das Generieren opaker Signer-IDs vor der Registrierung und das Ausstellen von Verifikations-Badges nach dem Upload. Beide Operationen nutzen die Trusteddit-API, doch Ihr Backend agiert als vertrauenswürdiger Vermittler: Es ist die einzige Instanz, die eine Signer-ID mit einem realen Benutzer verknüpfen kann.
Dieser Leitfaden behandelt den Signer-ID-Endpunkt, die Badge-Generierung, die Integration des Manifest-Speichers (manifest store), die TSA-Konfiguration, das Datenbankschema und die AWS-Architektur. Als Referenz dient ein Node.js / TypeScript-Backend auf AWS App Runner; die Muster gelten jedoch für jeden HTTP-Server.
Signer-ID-Endpunkt (Signer ID Endpoint)
POST /api/signer-id
Dieser Endpunkt wird von der mobilen App aufgerufen, nachdem sich der Benutzer authentifiziert hat. Er leitet einen opaken HMAC-SHA256-Token von der authentifizierten Benutzer-ID ab, speichert die bidirektionale Zuordnung und gibt den Token zurück. Die mobile App verwendet diesen Token als Zertifikats-CN im CSR.
// src/routes/signerId.ts (Express / Hono / Fastify)
import { createHmac, randomBytes } from 'crypto';
import { db } from '../db';
const HMAC_SECRET = process.env.SIGNER_ID_HMAC_SECRET!; // 32+ random bytes
/**
* Derive a deterministic opaque signer ID for a given user.
* The HMAC ensures the same user always gets the same signer ID,
* enabling idempotent re-enrollment without creating duplicate rows.
*/
function deriveSignerId(userId: string): string {
const hmac = createHmac('sha256', HMAC_SECRET)
.update(userId)
.digest('hex')
.slice(0, 16); // 64 bits of entropy – sufficient for uniqueness at scale
return `ph_${hmac}@thephenom.app`;
}
export async function handleSignerIdRequest(req, res) {
// userId is populated by your Cognito JWT middleware
const { userId } = req.user;
const signerId = deriveSignerId(userId);
// Upsert – safe to call multiple times (e.g. after re-enrollment)
await db.query(
`INSERT INTO signer_identities (signer_id, user_id, created_at)
VALUES ($1, $2, NOW())
ON CONFLICT (signer_id) DO NOTHING`,
[signerId, userId]
);
return res.json({ signerId });
}Sicherheitshinweis: Speichern Sie SIGNER_ID_HMAC_SECRET im AWS Secrets Manager oder Parameter Store. Rotieren Sie ihn nur, wenn Sie bestehende Signer-IDs bewusst ungültig machen möchten (was eine Neuregistrierung aller Geräte erfordert).
Badge-Generierung (Badge Generation)
Nachdem ein signierter Medien-Upload Ihre interne Prüfung bestanden hat, rufen Sie die Trusteddit-Badge-Signierdienst-API auf, um eine Verifikationssignatur der Plattform anzufügen. Der Badge-Signierdienst ergänzt das Manifest um eine zweite C2PA-Assertion unter Verwendung des serverseitigen Signaturzertifikats von Trusteddit.
// src/services/badge.ts
const BADGE_SIGNER_URL = 'https://badge.trusteddit.com/api/v1/sign';
export interface BadgeResult {
badgeUrl: string;
updatedManifest: string; // base64-encoded manifest with badge signature appended
verificationUrl: string;
}
/**
* Request a platform verification badge from the Trusteddit badge-signer.
* Call this only after your platform has reviewed and approved the media.
*
* @param mediaId - Your internal media ID
* @param manifestBox - The device-signed C2PA manifest from the upload
* @param mediaHash - SHA-256 hash of the media file (hex)
* @param apiKey - Your Trusteddit platform API key (from dashboard)
*/
export async function requestBadge(
mediaId: string,
manifestBox: string,
mediaHash: string,
apiKey: string
): Promise<BadgeResult> {
const response = await fetch(BADGE_SIGNER_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
mediaId,
manifestBox,
mediaHash,
platform: 'thephenom.app',
}),
});
if (!response.ok) {
const err = await response.json() as { code: string; message: string };
throw new Error(`Badge signing failed [${err.code}]: ${err.message}`);
}
return response.json() as Promise<BadgeResult>;
}Manifest-Speicher-Integration (Manifest Store Integration)
Der Trusteddit-Manifest-Speicher (manifest store) verwaltet den Lebenszyklus des C2PA-Manifests: Vorab-Registrierung vor dem Upload, Signierung nach Empfang und Finalisierung nach Anbringen des Badges. Dieser dreistufige Ablauf stellt sicher, dass der Manifest-Hash festgelegt ist, bevor die Mediendatei geschrieben wird.
// src/services/manifestStore.ts
const MANIFEST_STORE_URL = 'https://manifests.trusteddit.com/api/v1';
/**
* Step 1 – Pre-register: Reserve a manifest slot before the media upload.
* Returns a manifestId to include in the upload multipart form.
*/
export async function preRegisterManifest(
signerId: string,
mimeType: string,
apiKey: string
): Promise<string> {
const res = await fetch(`${MANIFEST_STORE_URL}/pre-register`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ signerId, mimeType }),
});
const { manifestId } = await res.json() as { manifestId: string };
return manifestId;
}
/**
* Step 2 – Sign: Attach the device-signed manifest box to the registered slot.
*/
export async function signManifest(
manifestId: string,
manifestBox: string,
apiKey: string
): Promise<void> {
await fetch(`${MANIFEST_STORE_URL}/${manifestId}/sign`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ manifestBox }),
});
}
/**
* Step 3 – Finalize: Attach the badge signature and mark the manifest complete.
* After this call, the manifest is publicly resolvable by verifying applications.
*/
export async function finalizeManifest(
manifestId: string,
badgeManifest: string,
apiKey: string
): Promise<string> {
const res = await fetch(`${MANIFEST_STORE_URL}/${manifestId}/finalize`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ badgeManifest }),
});
const { verificationUrl } = await res.json() as { verificationUrl: string };
return verificationUrl;
}TSA-Konfiguration (TSA Configuration)
Trusteddit betreibt einen RFC-3161-Zeitstempeldienst (Time-Stamp Authority, TSA) unter tsa.trusteddit.com. Ein Zeitstempel im C2PA-Manifest weist den Signierzeitpunkt unabhängig von der Geräte-Uhr nach, was für die forensische Beweiskraft entscheidend ist.
// Include in the C2PA claim (mobile, Step 3 / Step 5)
const claim = {
// ...
signature_info: {
alg: 'ES256',
cert: certPem,
chain,
// RFC 3161 TSA endpoint – Trusteddit issues RFC 3161 tokens
tsa_url: 'https://tsa.trusteddit.com/rfc3161',
},
};
// The C2PA SDK / @peculiar/x509 will call the TSA endpoint
// automatically when tsa_url is present in signature_info.
// The returned timestamp token is embedded in the manifest CMS structure.
// To verify a timestamp token independently:
// openssl ts -verify -in timestamp.tsr -data claim.json \
// -CAfile trusteddit-tsa-root.pemDatenbankschema (Database Schema)
Die Tabelle signer_identities ist die einzige Stelle, an der opake Signer-IDs mit realen Benutzeridentitäten verknüpft sind. Beschränken Sie den Zugriff auf diese Tabelle streng: Behandeln Sie sie wie einen Schlüsselspeicher.
-- PostgreSQL schema
CREATE TABLE signer_identities (
-- The opaque token used as certificate CN
-- Format: ph_<16-char hex>@thephenom.app
signer_id TEXT PRIMARY KEY,
-- Your internal user ID (Cognito sub or DB primary key)
user_id TEXT NOT NULL,
-- UTC creation timestamp
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Tracks the currently active enrollment for this signer ID.
-- A signer may re-enroll (e.g. after certificate expiry);
-- only the most recent certificate serial is stored here.
cert_serial TEXT,
cert_expires TIMESTAMPTZ,
-- Optional: last known device platform for auditing
platform TEXT
);
-- Index for reverse-lookup: given a user_id, find their signer_id(s)
CREATE INDEX idx_signer_identities_user_id ON signer_identities (user_id);
-- Row-level security: only the API role may read this table
ALTER TABLE signer_identities ENABLE ROW LEVEL SECURITY;
CREATE POLICY api_only ON signer_identities
USING (current_user = 'api_service_role');AWS-Architektur (AWS Architecture)
Eine Referenzarchitektur für das Hosting der Backend-API zur Unterstützung des Trusteddit-Registrierungsablaufs. Alle Komponenten laufen in einer einzelnen AWS-Region.
Identitäts- und Zugriffsverwaltung für App-Nutzer. Stellt JWTs aus, die von der Registrierungs-API verarbeitet werden.
Verwaltete Container-Laufzeitumgebung für Ihre Backend-API. Skaliert automatisch mit dem Datenverkehr; kein VPC für Basis-Bereitstellungen erforderlich.
Objektspeicher für signierte Mediendateien und C2PA-Manifeste. Lebenszyklus-Regeln verwalten das Löschen unvollständiger Uploads.
Persistenter Speicher für die Zuordnungstabelle signer_identities. RDS Proxy für Verbindungspooling in serverlosen Umgebungen nutzen.
# Simplified architecture (AWS CDK / Pulumi pseudocode)
Cognito User Pool
├── App Client (mobile) → issues ID tokens (JWT)
└── Triggers → post-confirmation Lambda (optional)
App Runner Service → your Node.js API
├── Routes:
│ POST /api/signer-id → derive & store signer ID
│ POST /api/media/upload → receive signed media + manifest
│ GET /api/media/:id → fetch media with badge
├── IAM Role:
│ secretsmanager:GetSecretValue (SIGNER_ID_HMAC_SECRET)
│ s3:PutObject (media bucket)
│ rds-db:connect (PostgreSQL)
└── Environment:
COGNITO_USER_POOL_ID, COGNITO_CLIENT_ID
SIGNER_ID_HMAC_SECRET (from Secrets Manager)
TRUSTEDDIT_PLATFORM_API_KEY (from Secrets Manager)
DATABASE_URL
S3 Bucket (media)
├── Lifecycle: abort incomplete uploads after 24h
├── Encryption: SSE-S3 (or SSE-KMS for regulated workloads)
└── Block public access: true (presigned URLs for retrieval)
RDS PostgreSQL (or Aurora Serverless v2)
└── Tables: signer_identities, media_uploads