Brief #21 — OpenID4VCI Issuer Endpoint¶
SURFACE: keys.ucca.online (ucca-keys worker) DO NOT TOUCH: ucca-site, ucca-engine, ucca-ops, ucca-ir, rtopacks-site, ucca-api, ucca-corporate Cloudflare account: e5a9830215a8d88961dc6c80a8c7442a D1: engine-db (0efa8970-0053-4623-8436-4e877af10887)
→ ALEX¶
What this brief does¶
Adds an OpenID4VCI 1.0 compliant credential issuance endpoint to ucca-keys. UCCA becomes a standards-compliant credential issuer. A wallet (Mattr, Google Wallet compatible) can request a UCCA capability credential and receive a signed W3C VC 2.0 JSON-LD or SD-JWT VC in return, delivered via the OpenID4VCI protocol. The OIDF conformance suite must pass against this implementation.
New endpoints on ucca-keys¶
1. /.well-known/openid-credential-issuer¶
OpenID4VCI issuer metadata document. Required by spec.
{
"issuer": "https://keys.ucca.online",
"credential_issuer": "https://keys.ucca.online",
"credential_endpoint": "https://keys.ucca.online/v1/credentials",
"token_endpoint": "https://keys.ucca.online/v1/token",
"jwks_uri": "https://keys.ucca.online/.well-known/jwks.json",
"credential_configurations_supported": {
"UCCACapabilityCredential": {
"format": "jwt_vc_json",
"scope": "UCCACapabilityCredential",
"cryptographic_binding_methods_supported": ["did:web", "jwk"],
"credential_signing_alg_values_supported": ["ES256"],
"credential_definition": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://ucca.online/ns/ucco/v1"
],
"type": ["VerifiableCredential", "UCCACapabilityCredential"]
},
"display": [
{
"name": "UCCA Capability Credential",
"locale": "en-AU",
"logo": {
"uri": "https://ucca.online/logo.png",
"alt_text": "UCCA Logo"
},
"background_color": "#0a0a0a",
"text_color": "#c8dff0"
}
]
}
}
}
2. /.well-known/jwks.json¶
Public key set for signature verification. Derive from the existing signing key already used in did:web:ucca.online. Must expose the same key as the DID doc's verificationMethod. Format:
{
"keys": [
{
"kty": "EC",
"crv": "P-256",
"kid": "signing-key-v1",
"use": "sig",
"alg": "ES256",
"x": "<x coordinate>",
"y": "<y coordinate>"
}
]
}
3. POST /v1/token¶
OAuth 2.0 token endpoint. Supports urn:ietf:params:oauth:grant-type:pre-authorized_code flow (pre-auth code flow — the simplest valid OpenID4VCI flow, no user login required).
Request body (form-encoded):
grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code
&pre-authorized_code=<code>
&user_pin=<pin if required>
Response:
{
"access_token": "<opaque token>",
"token_type": "Bearer",
"expires_in": 86400,
"c_nonce": "<random nonce>",
"c_nonce_expires_in": 86400
}
Token and pre-auth codes stored in engine-db D1 table vc_tokens:
CREATE TABLE IF NOT EXISTS vc_tokens (
id TEXT PRIMARY KEY,
pre_auth_code TEXT UNIQUE NOT NULL,
access_token TEXT UNIQUE,
c_nonce TEXT,
envelope_id TEXT NOT NULL,
holder_did TEXT,
used INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now')),
expires_at TEXT NOT NULL
);
4. POST /v1/credentials¶
Credential endpoint. Accepts Bearer token + proof of key possession. Returns signed VC.
Request:
{
"format": "jwt_vc_json",
"credential_definition": {
"type": ["VerifiableCredential", "UCCACapabilityCredential"]
},
"proof": {
"proof_type": "jwt",
"jwt": "<holder key binding JWT containing c_nonce>"
}
}
Steps:
1. Validate Bearer token against vc_tokens table — must exist, not used, not expired
2. Validate proof JWT — must contain matching c_nonce in claims
3. Extract envelope_id from token record
4. Call internal get_envelope(envelope_id) → retrieve TriumvirateEnvelope + JobEnvelope from engine-db
5. Call existing renderer/vc.py logic (replicate in JS or call engine API endpoint) to produce W3C VC 2.0 JSON-LD
6. Sign with UCCA signing key → attach proof block
7. Mark token as used in D1
8. Return:
{
"format": "jwt_vc_json",
"credential": "<signed JWT or JSON-LD VC>",
"c_nonce": "<new nonce>",
"c_nonce_expires_in": 86400
}
5. POST /v1/credential-offer¶
UCCA-internal endpoint (not part of OpenID4VCI spec, but needed to initiate flows). Called by the engine or ops console to generate a credential offer for a given envelope.
Request:
Response — credential offer URI ready to hand to a wallet:
{
"offer_uri": "openid-credential-offer://?credential_offer_uri=https://keys.ucca.online/v1/offers/<offer_id>",
"offer_id": "<uuid>",
"pre_authorized_code": "<code>",
"expires_at": "<ISO datetime>"
}
Offer stored in D1 vc_tokens table as pre-auth code record.
6. GET /v1/offers/<offer_id>¶
Returns credential offer object for wallets that fetch by URI:
{
"credential_issuer": "https://keys.ucca.online",
"credential_configuration_ids": ["UCCACapabilityCredential"],
"grants": {
"urn:ietf:params:oauth:grant-type:pre-authorized_code": {
"pre-authorized_code": "<code>",
"user_pin_required": false
}
}
}
Engine-side: new API endpoint¶
Add to ucca-api worker (or ucca-engine Python if Alex prefers):
POST /internal/vc-payload
Called by ucca-keys during credential issuance. Takes envelope_id, returns the VC payload JSON ready for signing (same output as renderer/vc.py). Auth: shared secret header X-UCCA-Internal.
This keeps the VC construction logic in Python (where it belongs) and ucca-keys just handles the OpenID4VCI protocol layer + signing.
New D1 table: vc_issuance_log¶
CREATE TABLE IF NOT EXISTS vc_issuance_log (
id TEXT PRIMARY KEY,
envelope_id TEXT NOT NULL,
holder_did TEXT,
format TEXT DEFAULT 'jwt_vc_json',
issued_at TEXT DEFAULT (datetime('now')),
offer_id TEXT,
wallet_hint TEXT,
cf_country TEXT,
cf_city TEXT
);
Log every issuance. This feeds the ops console VC status card (Brief #20 deferred stub).
Conformance test¶
Run the OIDF conformance suite against the live endpoint:
# Clone OIDF conformance suite
git clone https://gitlab.com/openid/conformance-suite.git
cd conformance-suite
# Run against UCCA issuer (pre-auth code flow)
./gradlew bootRun
# In the web UI at localhost:8080:
# Plan: OpenID4VCI Pre-Authorized Code Flow
# Issuer URL: https://keys.ucca.online
# credential_configuration_id: UCCACapabilityCredential
All mandatory tests must pass before this brief is marked closed. Paste results into the ops console VC status card.
Mattr wallet test (human presentation)¶
- Install Mattr Wallet (iOS or Android) — free, open source compatible
- Scan the credential offer QR code generated by
/v1/credential-offer - QR encodes:
openid-credential-offer://?credential_offer_uri=https://keys.ucca.online/v1/offers/<id> - Wallet fetches offer → hits token endpoint → hits credential endpoint → stores VC
- Verify the credential renders in wallet with UCCA issuer identity visible
- Screenshot for ops console
AI agent test¶
No wallet needed. Direct HTTP:
import httpx, json
# Step 1: Get credential offer
offer = httpx.post("https://keys.ucca.online/v1/credential-offer", json={
"envelope_id": "<test_envelope_id>",
"holder_did": "did:web:agent.example.com"
}).json()
# Step 2: Exchange pre-auth code for token
token = httpx.post("https://keys.ucca.online/v1/token", data={
"grant_type": "urn:ietf:params:oauth:grant-type:pre-authorized_code",
"pre-authorized_code": offer["pre_authorized_code"]
}).json()
# Step 3: Request credential (no proof required for agent flow — set user_pin_required: false)
credential = httpx.post("https://keys.ucca.online/v1/credentials",
headers={"Authorization": f"Bearer {token['access_token']}"},
json={
"format": "jwt_vc_json",
"credential_definition": {"type": ["VerifiableCredential", "UCCACapabilityCredential"]}
}
).json()
print(json.dumps(credential, indent=2))
Save this as scripts/test_oidc4vci_agent.py in ucca-engine.
New files¶
ucca-keys worker:
src/routes/openid/metadata.js ← /.well-known/openid-credential-issuer
src/routes/openid/jwks.js ← /.well-known/jwks.json
src/routes/openid/token.js ← POST /v1/token
src/routes/openid/credentials.js ← POST /v1/credentials
src/routes/openid/offer.js ← POST /v1/credential-offer + GET /v1/offers/:id
src/router.js ← add all new routes
ucca-engine (Python):
renderer/openid4vci.py ← VC payload builder for internal API
scripts/test_oidc4vci_agent.py ← agent flow smoke test
Definition of done¶
-
/.well-known/openid-credential-issuerreturns valid JSON -
/.well-known/jwks.jsonreturns public key matching DID doc - Pre-auth code flow completes end-to-end (offer → token → credential)
- OIDF conformance suite: pre-auth code flow — all mandatory tests pass
- Mattr wallet: credential received and renders with UCCA issuer
- AI agent test script runs and returns a signed VC
-
vc_issuance_logwrites on every issuance - Ops console VC status card shows issuance count + last issued timestamp
→ TIM¶
What this is in plain English¶
Right now UCCA can make a credential but there's no standardised way for anyone to collect it. A wallet app or an AI agent has no protocol to knock on the door and say "give me the credential I earned."
OpenID4VCI is that door. It's the standard that Google Wallet, the EU digital identity ecosystem, 38 governments, and increasingly AI agent frameworks use to collect credentials from issuers. It sits on top of OAuth 2.0 — the same plumbing the whole internet uses for "login with Google."
This brief adds that door to ucca-keys. The flow is:
- UCCA engine finishes a credential → generates a QR code or a link
- Person opens their Mattr wallet, scans the QR → wallet talks to ucca-keys
- ucca-keys checks the token, retrieves the TriumvirateEnvelope, hands a signed VC to the wallet
- The credential now lives in the wallet, verifiable by anyone, traceable back to the UCCA engine
For AI agents it's even simpler — no wallet, just an API call. Agent asks, engine answers, credential is machine-verified.
Why ucca-keys and not a new worker¶
ucca-keys is already the trust anchor — it holds the signing key, it serves the DID document, it's the thing the world already knows to trust for UCCA credentials. Adding OpenID4VCI here means the trust chain stays in one place. One worker, one signing key, one issuer identity.
Test targets and why¶
- Mattr wallet — open source, real production wallet, used by the New Zealand government, participated in the official OIDF interoperability tests. If it works with Mattr, it works with the ecosystem.
- OIDF conformance suite — the official free test suite from the OpenID Foundation. If UCCA passes this, we can legitimately say we are a conformant OpenID4VCI issuer. That sentence matters to enterprise buyers and government.
- AI agent script — proves the machine-to-machine path. No wallet, pure API. This is the agentic credential verification story.
What this unlocks¶
Google Wallet compatibility comes next (same protocol, different wallet app). Apple Wallet is a separate workstream (different format — mdoc — deferred). LinkedIn credential display is watch-and-wait. But the engine primitive — issue a credential that any conformant wallet or agent can collect and verify — is live after this brief.
OPS SURFACE RULE¶
After this brief deploys, add to ops.ucca.online → RTOpacks world block → VC Output:
- Count of VCs issued (from vc_issuance_log)
- Last issued timestamp
- Conformance suite result badge (PASS/FAIL)
- Sample credential offer QR generator (envelope_id input → QR out)
Brief #21 — UCCA Inc — 17 March 2026