Zurück zum Entwicklerportal (Developer Portal)
Infrastruktur-Leitfaden (Infrastructure Guide)

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.pem

Datenbankschema (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.

Cognito User Pool

Identitäts- und Zugriffsverwaltung für App-Nutzer. Stellt JWTs aus, die von der Registrierungs-API verarbeitet werden.

App Runner

Verwaltete Container-Laufzeitumgebung für Ihre Backend-API. Skaliert automatisch mit dem Datenverkehr; kein VPC für Basis-Bereitstellungen erforderlich.

S3

Objektspeicher für signierte Mediendateien und C2PA-Manifeste. Lebenszyklus-Regeln verwalten das Löschen unvollständiger Uploads.

RDS / PostgreSQL

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
Mobile-Integrationsleitfaden (Mobile Integration Guide)Weiter: API-Referenz (API Reference)