Identity & Trust — How It Works

This page explains how connectors prove their identity and establish trust using the Decentralized Claims Protocol (DCP).

Two DIDs Per Machine

Each machine has two separate identities with different jobs:

┌─────────────────────────────────────────────────────────────────┐
│  Machine (e.g. 192.168.1.50)                                    │
│                                                                 │
│  ┌───────────────────────────┐  ┌────────────────────────────┐  │
│  │  Participant DID          │  │  Issuer DID                │  │
│  │  did:web:host%3A7093      │  │  did:web:host%3A9876       │  │
│  │                           │  │                            │  │
│  │  Served by: Identity Hub  │  │  Served by: nginx          │  │
│  │  Key: Ed25519             │  │  Key: EC P-256             │  │
│  │  Port: 7093               │  │  Port: 9876                │  │
│  │                           │  │                            │  │
│  │  "This is who I am"       │  │  "I vouch for members"     │  │
│  │  Used for: DSP protocol   │  │  Used for: signing VCs     │  │
│  └───────────────────────────┘  └────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

The participant DID identifies the connector in protocol interactions (catalog requests, negotiations, transfers). Its Ed25519 key pair is auto-generated by the Identity Hub.

The issuer DID signs Verifiable Credentials. Its EC P-256 key pair is generated by generate-keys.sh and never leaves the machine. In standalone mode, each machine is its own issuer — it signs its own membership credential.


Setup Phase

Setting up a machine has four steps. Each step feeds into the next:

Step 1: Generate Keys

./generate-keys.sh

Generates key material on disk:

generate-keys.sh
    │
    ├──→ deployment/assets/issuer_private.pem    (EC P-256 key pair
    │    deployment/assets/issuer_public.pem      for signing VCs)
    │
    └──→ config/certs/private-key.pem            (key pair for Data Plane
         config/certs/public-key.pem              EDR token signing)

Step 2: Build Docker Images

./gradlew dockerize

Builds three Docker images from source:

Image Source
controlplane:latest runtimes/controlplane/
dataplane:latest runtimes/dataplane/
identityhub:latest runtimes/identityhub/

Each image: eclipse-temurin JRE + shadow JAR + health check.

Step 3: Start Services

docker compose up -d

Starts services in dependency order:

postgres, vault, did-server, http-receiver  ← start in parallel (no deps)
        │              │
        ▼              ▼
   identityhub              ← waits for postgres + vault healthy
        │
        ▼
   controlplane             ← waits for postgres + vault + identityhub + did-server
        │
        ▼
   dataplane                ← waits for postgres + vault + controlplane

Configuration is injected via environment variables:

  • MY_PUBLIC_HOST — DID identifiers, callback URLs, DP public URL
  • TRUSTED_ISSUER_DIDS — trusted issuer registry in Control Plane

Step 4: Seed Identity

./deployment/seed.sh

The seed script performs seven substeps:

  1. Generate MembershipCredential — signs a VC JWT with the issuer private key
  2. Create participant context in Identity Hub — generates Ed25519 key pair, returns client ID/secret
  3. Activate participant — marks the participant as active
  4. Publish participant DID — makes the DID document available at http://<host>:7093/.well-known/did.json
  5. Store STS client secret in Vault — so the Control Plane can authenticate to the STS
  6. Store MembershipCredential in wallet — Identity Hub can now present it during DCP auth
  7. Update issuer DID document on nginx — makes the issuer public key available at http://<host>:9876/.well-known/did.json

Runtime Flow — Catalog Request Between Two Machines

When Machine B (consumer) requests Machine A’s (provider) catalog, seven steps happen:

1. Get Token

Machine B’s Control Plane requests an SI (Self-Issued) token from its local STS (Identity Hub port 7096). The STS returns a JWT containing an inner access token.

2. Send DSP Request

Machine B sends the catalog request to Machine A’s DSP endpoint (port 19194) with the SI token as a Bearer token.

3. Verify SI Token

Machine A reads the iss claim from the token (did:web:B:7093), resolves Machine B’s participant DID document over HTTP, extracts the Ed25519 public key, and verifies the JWT signature.

4. Request Credentials

Machine A extracts the inner access token (which contains scope: MembershipCredential:read), reads the CredentialService URL from Machine B’s DID document, and sends a presentation request to Machine B’s Identity Hub (port 7091).

5. Present Credential

Machine B’s Identity Hub finds the MembershipCredential in its wallet and wraps it in a Verifiable Presentation (VP).

6. Verify MembershipCredential

Machine A checks:

  • Is the VC’s issuer (did:web:B:9876) in the trusted issuer registry?
  • Resolves the issuer DID document from nginx (port 9876) to get the P-256 public key
  • Verifies the VC JWT signature
  • Checks VC type and expiry

7. Return Catalog

All checks pass — Machine A returns its catalog to Machine B.

Machine B (consumer)                                Machine A (provider)

  Control Plane ──→ STS: "token for A"
  Control Plane ──→ A's DSP: catalog request + SI token
                                          A resolves B's DID ──→ verify SI token ✓
                                          A calls B's Credential API ──→ get VP
                                          A resolves B's issuer DID ──→ verify VC ✓
                    Catalog response ←── A returns catalog

The flow is fully symmetric. When Machine A requests Machine B’s catalog, the roles mirror exactly.


Token Structure

The SI token uses a token-in-token pattern:

┌─ Outer JWT (SI Token) ──────────────────────────────────────┐
│                                                             │
│  Header:  { alg: "EdDSA", kid: "did:web:B:7093#key-1" }     │
│                                                             │
│  Payload: {                                                 │
│    iss: "did:web:B:7093"          ← who is making request   │
│    aud: "did:web:A:7093"          ← who the request is for  │
│    exp: ...                                                 │
│                                                             │
│    token: ┌─ Inner JWT (Access Token) ────────────────────┐ │
│           │  scope: "org.eclipse.edc.vc.type:             │ │
│           │          MembershipCredential:read"           │ │
│           │                                               │ │
│           │  Tells the provider what credential           │ │
│           │  to request from the consumer's wallet        │ │
│           └───────────────────────────────────────────────┘ │
│  }                                                          │
│                                                             │
│  Signed with: Machine B's Ed25519 private key               │
└─────────────────────────────────────────────────────────────┘

Trust Chain

Machine A trusts Machine B because:

  1. B’s SI token is signed by B’s Ed25519 key — verified via DID resolution (did:web:B:7093)
  2. B has a MembershipCredential — presented from B’s Identity Hub wallet
  3. The VC is signed by B’s issuer (did:web:B:9876) — issuer is in A’s TRUSTED_ISSUER_DIDS list, signature verified via issuer DID resolution

Key Verification Summary

What is verified Key used Key type Public key source
SI token (DSP request auth) Participant key Ed25519 did:web:host%3A7093 — Identity Hub DID document
MembershipCredential (VC) Issuer key EC P-256 did:web:host%3A9876 — nginx DID server