tpt-identity

Go

Self-sovereign identity server for Go — OIDC provider, W3C Verifiable Credentials, SD-JWT, DID (web/key/peer/ion), DIDComm v2, guardian recovery, and consent management. Pure-Go SQLite, no CGo.

0 stars0 forks0 watchersMIT License
decentralized-identitydiddidcommgolangidentityoidcsd-jwtself-sovereign-identityverifiable-credentialswebauthn

Languages

Go99.8%Dockerfile0.2%
README

tpt-identity

MIT License Go 1.22

Sovereign identity backbone for the TPT open-source ecosystem. Every person gets one DID that works across email, healthcare, payments, and civic systems — authenticated however makes sense for them.

What it does

  • DID management — create and resolve did:web, did:key, did:peer identifiers (optional: did:ion via build tag)
  • Verifiable Credentials — issue and verify W3C VC Data Model 2.0 with DataIntegrityProof (eddsa-jcs-2022) and SD-JWT selective disclosure
  • Consent receipts — cryptographically signed records of who accessed what, under what legal basis (NZ Privacy Act 2020 compliant)
  • OIDC Provider — authorization code flow with mandatory PKCE, dynamic client registration (RFC 7591), token introspection (RFC 7662), back-channel logout (OIDC BCL 1.0), client credentials grant (RFC 6749 §4.4), refresh token rotation
  • Identity bridges — accept Google, GitHub, Azure AD, SAML 2.0, LDAP/AD, magic link, password, and passkey (WebAuthn) auth; each maps to a platform DID
  • Account linking — one DID, multiple external auth providers
  • MFA — TOTP (RFC 6238) with AES-256-GCM encrypted secrets; brute-force lockout
  • Duress code — secondary passphrase that creates a normal session but fires a silent session.duress alert
  • Guardian recovery — M-of-N Shamir secret sharing so trusted guardians can recover a lost identity key
  • Verifiable audit log — every event is appended to a SHA-256 hash-chained log; tampering is detectable
  • Credential marketplace — discoverable registry of which issuers offer which credential schemas
  • Webhook events — push credential.issued, consent.granted, session.duress etc. to subscribers with HMAC-SHA256 signatures
  • Presentation Exchange — DIF PE v2 request/submit flow for structured credential requests
  • Inter-service trust — short-lived Ed25519-signed permits and federated reputation via DNS TXT or VC

Docker quickstart

# 1. Generate signing keys
mkdir -p keys
go run ./cmd/tpt-identity keygen --method web --domain example.com \
  --out-sign keys/ed25519.pem --out-enc keys/x25519.pem --passphrase ""

# 2. Copy and edit config
cp config.yaml.example config.yaml
# Set issuer, db_path, api_key, identity.signing_key, etc.

# 3. Build and start
docker compose up --build

# Server is now at http://localhost:8080

Binary quickstart

# Install
go install github.com/PhillipC05/tpt-identity/cmd/tpt-identity@latest

# 1. Generate keys
tpt-identity keygen --method web --domain example.com \
  --out-sign keys/ed25519.pem --out-enc keys/x25519.pem --passphrase ""

# 2. Edit config
cp config.yaml.example config.yaml

# 3. Run migrations
tpt-identity migrate up

# 4. Start server
tpt-identity serve --config config.yaml

Optional build tags

go build -tags "ion"           # include did:ion (Bitcoin/Sidetree)
go build -tags "saml"          # include SAML 2.0 identity bridge
go build -tags "ldap"          # include LDAP/Active Directory bridge
go build -tags "ion,saml,ldap" # all optional features

Integration guide (OIDC)

Register your application, then follow the standard PKCE authorization code flow:

# Step 1: Register a client (once)
curl -s -X POST https://identity.example.com/oidc/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My App",
    "redirect_uris": ["https://myapp.example.com/callback"],
    "token_endpoint_auth_method": "none"
  }' | tee client.json

CLIENT_ID=$(jq -r .client_id client.json)

# Step 2: Generate PKCE verifier + challenge
VERIFIER=$(openssl rand -base64 32 | tr -d '=+/' | head -c 43)
CHALLENGE=$(echo -n "$VERIFIER" | sha256sum -b | xxd -r -p | base64 -w 0 | tr '+/' '-_' | tr -d '=')

# Step 3: Redirect user to /authorize
echo "https://identity.example.com/authorize?response_type=code\
&client_id=$CLIENT_ID\
&redirect_uri=https://myapp.example.com/callback\
&scope=openid\
&state=$(openssl rand -hex 16)\
&code_challenge=$CHALLENGE\
&code_challenge_method=S256"

# Step 4: Exchange code for tokens
curl -s -X POST https://identity.example.com/token \
  -d "grant_type=authorization_code" \
  -d "code=<code_from_callback>" \
  -d "redirect_uri=https://myapp.example.com/callback" \
  -d "client_id=$CLIENT_ID" \
  -d "code_verifier=$VERIFIER"

Webhook verification

Every webhook delivery includes an X-TPT-Signature-256 header:

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "net/http"
)

func verifyWebhook(r *http.Request, signingSecret string) bool {
    sig := r.Header.Get("X-TPT-Signature-256")
    body, _ := io.ReadAll(r.Body)
    mac := hmac.New(sha256.New, []byte(signingSecret))
    mac.Write(body)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(sig), []byte(expected))
}

Configuration

Copy config.yaml.example to config.yaml. All keys can be overridden via environment variables prefixed TPT_IDENTITY_ (e.g. TPT_IDENTITY_API_KEY).

KeyDescription
issuerCanonical HTTPS URL — used as OIDC issuer and did:web base (required)
listen_addrHTTP bind address (default :8080)
db_pathSQLite database file path
api_keyBearer token for admin/service endpoints
identity.signing_keyPath to Ed25519 private key PEM
identity.passphraseArgon2id passphrase for key decryption
totp_passphrasePassphrase for TOTP secret encryption (defaults to identity.passphrase)
rate_limitRequests per second per IP (default 50)
server.trusted_proxiesCIDR list of trusted reverse proxies for X-Forwarded-For
cors.allowed_originsAllowed CORS origins (empty = CORS disabled)
bridges.oidc[]OIDC relying-party bridges — name, issuer, client_id, client_secret, scopes
bridges.password.enabledEnable legacy password bridge (default false)
consent.expiry_warning_daysFire consent.expiring_soon events N days before expiry (default 7)
marketplace.advertiseEnable the credential marketplace registry (default false)
log.leveldebug, info, warn, error (default info)
log.formatjson or text (default json)

Deployment notes

HTTPS (required)

Run behind Caddy or nginx. Caddy example:

identity.example.com {
    reverse_proxy localhost:8080
}

SQLite WAL mode

The server enables WAL mode automatically. For best performance, mount the database on a local filesystem (not NFS). The store uses a single writer connection intentionally — do not set SetMaxOpenConns higher than 1.

Key file permissions

chmod 600 keys/ed25519.pem keys/x25519.pem

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                           tpt-identity                               │
│                                                                      │
│  ┌──────────────────── Identity Bridge ───────────────────────────┐  │
│  │ OIDC RP (Google/GitHub/Azure) │ Magic Link │ SAML† │ LDAP/AD† │  │
│  │ Password (opt-in) │ WebAuthn/Passkeys │ TOTP MFA                │  │
│  └────────────────────────────┬───────────────────────────────────┘  │
│                               │ ExternalIdentity → platform DID      │
│  ┌─────────┐  ┌────────────┐  ┌───────────┐  ┌──────────────────┐   │
│  │   DID   │  │  VC + SD-  │  │  Consent  │  │  OIDC Provider   │   │
│  │  layer  │  │    JWT     │  │ grants +  │  │ PKCE · RFC 7591  │   │
│  │ w/k/p/i │  │ issue/vrfy │  │ receipts  │  │ introspect · BCL │   │
│  └────┬────┘  └─────┬──────┘  └─────┬─────┘  └────────┬─────────┘   │
│       │              │               │                  │             │
│  ┌────┴──────────────┴───────────────┴──────────────────┴──────────┐  │
│  │              SQLite store (PostgreSQL-compatible schema)         │  │
│  └─────────────────────────────────────────────────────────────────┘  │
│                                                                      │
│  ┌───────────────────────────────────────────────────────────────┐   │
│  │  REST API · rate limit · audit log · webhook events · metrics  │   │
│  └───────────────────────────────────────────────────────────────┘   │
│  ┌──────────────────┐  ┌─────────────────┐  ┌─────────────────────┐  │
│  │  Duress alerts   │  │ Guardian recovery│  │  Credential market  │  │
│  └──────────────────┘  └─────────────────┘  └─────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘
 † requires build tag

API endpoints

Public — no auth required

MethodPathDescription
GET/.well-known/openid-configurationOIDC discovery document
GET/.well-known/jwks.jsonJSON Web Key Set
GET/.well-known/did.jsonPlatform DID document
GET/authorizeOIDC authorization (PKCE + state required)
POST/tokenToken exchange, refresh, client credentials
POST/oauth/introspectRFC 7662 token introspection
GET/userinfoSubject DID + AMR claims
POST/oidc/registerRFC 7591 dynamic client registration
POST/oidc/revokeRFC 7009 token revocation
GET/healthzLiveness probe
GET/readyzReadiness probe (DB check)
GET/api/v1/status/{listId}BitstringStatusList credential
GET/api/v1/marketplaceCredential marketplace (issuers + schemas)

Identity bridge — public

MethodPathDescription
GET/auth/{provider}Start redirect-based bridge flow
GET/auth/{provider}/callbackOAuth2/OIDC callback
POST/auth/magiclink/requestRequest a magic link
GET/auth/magiclink/verifyVerify magic link token
POST/auth/passwordPassword authentication
POST/auth/webauthn/register/beginStart passkey registration
POST/auth/webauthn/register/finishComplete passkey registration
POST/auth/webauthn/login/beginStart passkey login
POST/auth/webauthn/login/finishComplete passkey login

Self-service — bearer token (user's own access token)

MethodPathDescription
GET/api/v1/me/sessionsList active sessions
DELETE/api/v1/me/sessions/{id}Revoke a session
GET/api/v1/me/linksList linked external providers
DELETE/api/v1/me/links/{provider}Unlink a provider
POST/api/v1/me/totp/enrolEnrol TOTP — returns otpauth:// URI
POST/api/v1/me/totp/verifyVerify a TOTP code
DELETE/api/v1/me/totpRemove TOTP enrolment
POST/api/v1/me/duress/enrolSet duress passphrase
DELETE/api/v1/me/duressRemove duress passphrase
POST/api/v1/me/recovery/enrolConfigure guardian recovery (M-of-N)
GET/api/v1/me/recoveryGet recovery configuration

Protected — Bearer api_key

MethodPathDescription
POST/api/v1/identitiesRegister a DID
GET/api/v1/identities/{did}Resolve identity
POST/api/v1/credentialsIssue a W3C VC
POST/api/v1/credentials/verifyVerify a W3C VC
GET/api/v1/credentialsList credentials for a subject
DELETE/api/v1/credentials/{id}Delete a credential
POST/api/v1/credentials/sd-jwtIssue an SD-JWT credential
POST/api/v1/credentials/sd-jwt/verifyVerify an SD-JWT presentation
GET/POST/DELETE/api/v1/consents/grantsConsent grant management
GET/api/v1/consents/receiptsConsent receipt audit trail
GET/api/v1/schemasFull schema taxonomy
POST/GET/DELETE/api/v1/webhooksWebhook subscription management
POST/api/v1/presentations/requestDIF PE v2 presentation request
POST/api/v1/presentations/submitSubmit a VP
DELETE/api/v1/sessions/{id}Admin session revocation
GET/api/v1/audit-logPaginated verifiable audit log
GET/api/v1/audit-log/proof/{seq}Hash-chain proof from seq to head
POST/api/v1/recovery/{id}/approveGuardian approves a recovery request
GET/api/v1/recovery/{id}Get recovery request status
POST/api/v1/recovery/initiateInitiate M-of-N recovery
GET/metricsPrometheus metrics (api_key gated)

Security notes

  • PKCE (S256) and state are mandatory on every authorization code flow
  • Dynamic client registration is required — unregistered client_id values are rejected
  • Refresh tokens are single-use, stored by sha256(raw), with stolen-token detection via grace window
  • did:key is blocked as a VC issuer DID — use did:web or did:peer
  • did:web resolution enforces RFC-1918 blocking and 5-second timeout (SSRF hardening)
  • Webhook URLs are validated at registration time; loopback/RFC-1918 targets are rejected
  • Argon2id (64 MB / 3 iterations / 4 threads) for all passphrase-based key derivation
  • LDAP bridge requires ldaps:// — plaintext ldap:// is refused at startup
  • Duress passphrase creates a normal-looking session while triggering a silent session.duress event
  • Extra-sensitive schemas (mental-health, addiction, criminal-record, etc.) require individual explicit grants
  • Consent revocations set revokedAt — records are never deleted (NZ Privacy Act 2020 audit trail)
  • Audit log events are SHA-256 hash-chained; any tampering breaks the chain

Credential schema taxonomy

50+ schemas across 11 categories. See SCHEMA.md for the full reference.

Categories: identity · healthcare · finance · professional · education · legal · property · civic · social · travel · insurance

PWA / Mobile

tpt-identity exposes a pure REST + OIDC API. The recommended mobile integration is a Progressive Web App that communicates directly with the API — no native SDK or app-store distribution required.

License

MIT — see LICENSE.