Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
eb31018
feat(oid4vc): add mDOC (ISO 18013-5) credential issuance and verifica…
burdettadam Mar 5, 2026
15ad8ec
fix(mso_mdoc): replace _patch_mdoc_keys with Mdoc.issuer_signed_b64()
burdettadam Mar 6, 2026
9811d5e
refactor(mso_mdoc): drop cbor2 as runtime dependency
burdettadam Mar 6, 2026
87547fa
chore: remove DEEP_REVIEW.md from tracking
burdettadam Mar 6, 2026
d078461
feat(mso_mdoc): drop FileTrustStore, make trust registry wallet-only
burdettadam Mar 6, 2026
085dca5
fix(mso_mdoc): fix mDL issuance for isomdl-uniffi create_and_sign_mdl…
burdettadam Mar 9, 2026
749c0be
fix(demo): change issuer default host ports from 8021/8022 to 8121/8122
burdettadam Mar 9, 2026
d32f8ed
fix(demo): align WALLET_PORT and wallet callback URL defaults to 7201
burdettadam Mar 9, 2026
2c332ca
fix(demo): sync .env.example defaults with docker-compose.yml
burdettadam Mar 9, 2026
7f04fcd
fix(demo): fix remaining stale port refs in .env.example
burdettadam Mar 9, 2026
f0fe85b
fix(demo): update README port refs to match docker-compose defaults
burdettadam Mar 9, 2026
400329c
fix(oid4vc): update README spec reference from Draft 11 to OID4VCI 1.0
burdettadam Mar 9, 2026
f772018
fix(mso_mdoc): convert claims dict to array in issuer metadata
burdettadam Mar 9, 2026
aafae3a
fix(mso_mdoc): revert wrong SD-JWT claims format applied to mDOC meta…
burdettadam Mar 9, 2026
7bb2e25
fix(mso_mdoc): convert claims to spec-compliant array in issuer metadata
burdettadam Mar 10, 2026
fb40afd
fix(mso_mdoc): nest claims and display inside credential_metadata
burdettadam Mar 10, 2026
cd4af7a
fix(oid4vc): normalize default port in proof JWT aud check
burdettadam Mar 10, 2026
115ef2f
fix(oid4vc): resolve proof key from payload iss when header has no ke…
burdettadam Mar 10, 2026
fc00562
refactor(mso_mdoc): remove unnecessary global, fix startup error hand…
burdettadam Mar 10, 2026
d41d01b
refactor(mso_mdoc): warn instead of auto-generating keys on startup
burdettadam Mar 10, 2026
bb01117
oid4vc: add clear JWT header validation with required field checks
burdettadam Mar 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions .github/workflows/oid4vc-conformance-tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
name: OID4VC Conformance Tests
# Runs the OIDF HAIP conformance suite against ACA-Py OID4VCI issuer and
# OID4VP verifier. The suite is started from source inside Docker Compose and
# all test results are written to a JUnit XML artifact.
#
# Trigger conditions:
# - PR or push that touches oid4vc/** source files
# - Manual run via workflow_dispatch (always runs regardless of changed files)
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- "**"
paths:
- "oid4vc/**"
push:
branches:
- main
paths:
- "oid4vc/**"
workflow_dispatch:

jobs:
conformance-tests:
name: "OID4VC Conformance Tests"
runs-on: ubuntu-latest
# Skip draft PRs (same policy as integration-tests)
if: |
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push') ||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false)
timeout-minutes: 90

steps:
# ── Checkout ────────────────────────────────────────────────────────────
- name: Check out repository
uses: actions/checkout@v4

# ── Docker Buildx (enables layer cache via GitHub Actions cache) ────────
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

# ── Pre-build ACA-Py issuer image (Rust/isomdl, ~10 min cold) ──────────
# Both issuer and verifier share the same Dockerfile; the verifier build
# hits cache after the issuer build completes.
- name: Build acapy-issuer image
uses: docker/build-push-action@v6
with:
context: .
file: oid4vc/docker/Dockerfile
push: false
load: true
tags: oid4vc-integration-acapy-issuer:latest
build-args: |
ACAPY_VERSION=1.4.0
ISOMDL_BRANCH=fix/python-build-system
cache-from: type=gha,scope=acapy-oid4vc
cache-to: type=gha,mode=max,scope=acapy-oid4vc

- name: Build acapy-verifier image
uses: docker/build-push-action@v6
with:
context: .
file: oid4vc/docker/Dockerfile
push: false
load: true
tags: oid4vc-integration-acapy-verifier:latest
build-args: |
ACAPY_VERSION=1.4.0
ISOMDL_BRANCH=fix/python-build-system
# Issuer + verifier share all layers; use same cache scope.
cache-from: type=gha,scope=acapy-oid4vc

# ── Pre-build OIDF conformance server (Maven build, ~15 min cold) ───────
- name: Build conformance-server image
uses: docker/build-push-action@v6
with:
context: oid4vc/integration/conformance
file: oid4vc/integration/conformance/Dockerfile.server
push: false
load: true
tags: oid4vc-integration-conformance-server:latest
build-args: |
CONFORMANCE_SUITE_BRANCH=master
cache-from: type=gha,scope=conformance-server
cache-to: type=gha,mode=max,scope=conformance-server

# ── Pre-build conformance runner (lightweight Python image) ─────────────
- name: Build conformance-runner image
uses: docker/build-push-action@v6
with:
context: oid4vc/integration
file: oid4vc/integration/conformance/Dockerfile.runner
push: false
load: true
tags: oid4vc-integration-conformance-runner:latest
cache-from: type=gha,scope=conformance-runner
cache-to: type=gha,mode=max,scope=conformance-runner

# ── Run conformance suite ────────────────────────────────────────────────
# DOCKER_PLATFORM is detected automatically by the shell script based on
# `uname -m`; set explicitly here to avoid any ambiguity on CI runners.
- name: Run conformance tests
env:
DOCKER_PLATFORM: linux/amd64
run: |
bash oid4vc/integration/run-conformance-tests.sh run all
# ── Collect results ──────────────────────────────────────────────────────
- name: Upload JUnit test results
if: always()
uses: actions/upload-artifact@v4
with:
name: conformance-junit-results
path: oid4vc/integration/test-results/conformance-junit.xml
if-no-files-found: warn

- name: Publish JUnit test summary
if: always()
uses: mikepenz/action-junit-report@v4
with:
report_paths: "oid4vc/integration/test-results/conformance-junit.xml"
check_name: "OIDF Conformance Results"
fail_on_failure: false
require_tests: false

# ── Collect Docker logs on failure ───────────────────────────────────────
- name: Dump Docker Compose logs
if: failure()
run: |
mkdir -p /tmp/conformance-logs
cd oid4vc/integration
# Capture all service logs for post-mortem analysis
docker compose --profile conformance logs --no-color \
> /tmp/conformance-logs/docker-compose.log 2>&1 || true
docker compose --profile conformance logs --no-color acapy-issuer \
> /tmp/conformance-logs/acapy-issuer.log 2>&1 || true
docker compose --profile conformance logs --no-color acapy-verifier \
> /tmp/conformance-logs/acapy-verifier.log 2>&1 || true
docker compose --profile conformance logs --no-color conformance-server \
> /tmp/conformance-logs/conformance-server.log 2>&1 || true
- name: Upload Docker logs artifact
if: failure()
uses: actions/upload-artifact@v4
with:
name: conformance-docker-logs
path: /tmp/conformance-logs/
retention-days: 7
2 changes: 0 additions & 2 deletions .github/workflows/pr-linting-and-unit-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ jobs:
#----------------------------------------------
- name: Unit test plugins
id: unit-tests
continue-on-error: true
run: |
for dir in ${{ steps.changed-plugins.outputs.changed-plugins }}; do
cd $dir
Expand All @@ -110,7 +109,6 @@ jobs:
integration-tests:
name: "Integration tests"
runs-on: ubuntu-latest
continue-on-error: true
needs: linting-and-unit-tests
if: needs.linting-and-unit-tests.result == 'success'
steps:
Expand Down
4 changes: 2 additions & 2 deletions oid4vc/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# OpenID4VCI Plugin for ACA-Py

This plugin implements [OpenID4VCI (Draft 11)][oid4vci]. The OpenID4VCI specification is in active development, as is this plugin. Consider this plugin experimental; endpoints and records may change to reflect upstream changes in the specification.
This plugin implements [OpenID4VCI 1.0][oid4vci]. This implementation follows the OpenID4VCI 1.0 final specification and is not backwards compatible with earlier drafts.

## Developer Documentation

Expand Down Expand Up @@ -435,4 +435,4 @@ For Apple Silicon, the `DOCKER_DEFAULT_PLATFORM=linux/amd64` environment variabl
- Batch Credential Issuance
- We're limited to DID Methods that ACA-Py supports for issuance (more can be added by Plugin, e.g. DID Web); `did:sov`, `did:key`

[oid4vci]: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-11.html
[oid4vci]: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html
22 changes: 11 additions & 11 deletions oid4vc/demo/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
# ── ACA-Py host port bindings ───────────────────────────────────────────────
# The admin APIs and OID4VC endpoints are exposed on the host for easy curl
# access and for the local Playwright demo script.
# If the default ports (8021/8022) are already occupied on your machine,
# If the default ports (8121/8122) are already occupied on your machine,
# override them here and also set the explicit URL vars below.

ACAPY_ISSUER_ADMIN_PORT=8021
ACAPY_ISSUER_OID4VCI_PORT=8022
ACAPY_ISSUER_ADMIN_PORT=8121
ACAPY_ISSUER_OID4VCI_PORT=8122

ACAPY_VERIFIER_ADMIN_PORT=8031
ACAPY_VERIFIER_OID4VP_PORT=8032
Expand All @@ -23,17 +23,17 @@ ACAPY_VERIFIER_OID4VP_PORT=8032
# Set these to match ACAPY_ISSUER_ADMIN_PORT / ACAPY_ISSUER_OID4VCI_PORT
# whenever you override the default ports above.

# ACAPY_ISSUER_ADMIN_URL=http://localhost:8021
# ACAPY_ISSUER_OID4VCI_URL=http://localhost:8022
# ACAPY_ISSUER_ADMIN_URL=http://localhost:8121
# ACAPY_ISSUER_OID4VCI_URL=http://localhost:8122

# ── Walt.id wallet port ─────────────────────────────────────────────────────
# The nginx proxy combines the wallet frontend and API on this port.
# Change if port 7101 is already in use on your machine.
WALLET_PORT=7101
# Change if port 7201 is already in use on your machine.
WALLET_PORT=7201

# Explicit wallet URLs for Playwright — must match WALLET_PORT above.
# WALTID_WALLET_URL=http://localhost:7101
# WALTID_WALLET_API_URL=http://localhost:7101
# WALTID_WALLET_URL=http://localhost:7201
# WALTID_WALLET_API_URL=http://localhost:7201

# ── Docker platform ─────────────────────────────────────────────────────────
# Default linux/arm64 (Apple Silicon native for ACA-Py).
Expand Down Expand Up @@ -63,9 +63,9 @@ WALLET_PORT=7101
# 1. Install zrok: https://docs.zrok.io/docs/getting-started
# 2. Reserve permanent tunnel names once (lowercase alphanumeric, 4-32 chars):
#
# zrok reserve public --unique-name "myissuerapi" http://localhost:8022
# zrok reserve public --unique-name "myissuerapi" http://localhost:8122
# zrok reserve public --unique-name "myverifierapi" http://localhost:8032
# zrok reserve public --unique-name "mydemowallet" http://localhost:7101
# zrok reserve public --unique-name "mydemowallet" http://localhost:7201
#
# 3. Activate all tunnels each session (in separate terminals):
#
Expand Down
22 changes: 11 additions & 11 deletions oid4vc/demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ docker compose up -d
./setup.sh

# 5. Open the wallet in your browser
open http://localhost:7101
open http://localhost:7201
```

Register a new account in the wallet and you're ready to go.
Expand All @@ -52,9 +52,9 @@ Register a new account in the wallet and you're ready to go.

| Service | URL | Purpose |
|---|---|---|
| walt.id Web Wallet | <http://localhost:7101> | Holder wallet (browser) |
| ACA-Py Issuer admin | <http://localhost:8021> | Issue credentials |
| ACA-Py Issuer OID4VCI | <http://localhost:8022> | OID4VCI v1 endpoint |
| walt.id Web Wallet | <http://localhost:7201> | Holder wallet (browser) |
| ACA-Py Issuer admin | <http://localhost:8121> | Issue credentials |
| ACA-Py Issuer OID4VCI | <http://localhost:8122> | OID4VCI v1 endpoint |
| ACA-Py Verifier admin | <http://localhost:8031> | Verify presentations |
| ACA-Py Verifier OID4VP | <http://localhost:8032> | OID4VP v1 endpoint |

Expand Down Expand Up @@ -106,9 +106,9 @@ an HTTPS endpoint — useful for testing with real mobile wallets.
# https://docs.zrok.io/docs/getting-started

# Reserve permanent tunnel names (one time)
zrok reserve public --unique-name "myissuerapi" http://localhost:8022
zrok reserve public --unique-name "myissuerapi" http://localhost:8122
zrok reserve public --unique-name "myverifierapi" http://localhost:8032
zrok reserve public --unique-name "mydemowallet" http://localhost:7101
zrok reserve public --unique-name "mydemowallet" http://localhost:7201

# Activate tunnels (each session, in separate terminals)
zrok share reserved myissuerapi
Expand Down Expand Up @@ -140,8 +140,8 @@ Restart the stack: `docker compose up -d` and re-run `./setup.sh`.
│ │
│ ┌─────────────────┐ OID4VCI v1 ┌─────────────┐ │
│ │ ACA-Py Issuer │ ◄──────────────── │ walt.id │ │
│ │ :8021 admin │ │ wallet-api │ │
│ │ :8022 OID4VCI │ │ :7001 │ │
│ │ :8121 admin │ │ wallet-api │ │
│ │ :8122 OID4VCI │ │ :7001 │ │
│ └─────────────────┘ └─────────────┘ │
│ │ │
│ ┌─────────────────┐ OID4VP v1 │ │
Expand Down Expand Up @@ -236,10 +236,10 @@ To issue a credential manually:

```bash
# Get the credential config IDs
curl -s http://localhost:8021/oid4vci/credential-supported/list | python3 -m json.tool
curl -s http://localhost:8121/oid4vci/credential-supported/list | python3 -m json.tool

# Create an offer (replace <did> and <config_id>)
curl -s -X POST http://localhost:8021/oid4vci/exchange/create \
curl -s -X POST http://localhost:8121/oid4vci/exchange/create \
-H "Content-Type: application/json" \
-d '{
"supported_cred_id": "<config_id>",
Expand All @@ -258,7 +258,7 @@ curl -s -X POST http://localhost:8021/oid4vci/exchange/create \
```

Then paste the `credential_offer` URL into the wallet at
`http://localhost:7101`.
`http://localhost:7201`.

---

Expand Down
8 changes: 4 additions & 4 deletions oid4vc/demo/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ services:
ACAPY_VERSION: 1.4.0
ISOMDL_BRANCH: fix/python-build-system
ports:
- "${ACAPY_ISSUER_ADMIN_PORT:-8021}:8021"
- "${ACAPY_ISSUER_OID4VCI_PORT:-8022}:8022"
- "${ACAPY_ISSUER_ADMIN_PORT:-8121}:8021"
- "${ACAPY_ISSUER_OID4VCI_PORT:-8122}:8022"
environment:
- AGENT_ENDPOINT=http://acapy-issuer:8020
# OID4VCI_ENDPOINT is the URL embedded in credential offers.
Expand Down Expand Up @@ -203,7 +203,7 @@ services:
environment:
- PORT=7101
# Must match the public-facing wallet URL so deep-links resolve correctly.
- NUXT_PUBLIC_ISSUER_CALLBACK_URL=${WALLET_PUBLIC_URL:-http://localhost:7101}
- NUXT_PUBLIC_ISSUER_CALLBACK_URL=${WALLET_PUBLIC_URL:-http://localhost:7201}
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:7101"]
interval: 10s
Expand All @@ -230,7 +230,7 @@ services:
waltid-proxy:
image: nginx:alpine
ports:
- "${WALLET_PORT:-7101}:80"
- "${WALLET_PORT:-7201}:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
Expand Down
21 changes: 12 additions & 9 deletions oid4vc/demo/playwright/demo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,15 +200,18 @@ test.describe('OID4VC mDOC Demo', () => {
// ── Create credential offer ──
const credentialSubject = {
'org.iso.18013.5.1': {
given_name: 'Alice',
family_name: 'Holder',
birth_date: '1990-06-15',
issuing_country: 'US',
issuing_authority: 'Demo DMV',
document_number: 'DL-DEMO-001',
issue_date: new Date().toISOString().split('T')[0],
expiry_date: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)
.toISOString().split('T')[0],
given_name: 'Alice',
family_name: 'Holder',
birth_date: '1990-06-15',
issuing_country: 'US',
issuing_authority: 'Demo DMV',
document_number: 'DL-DEMO-001',
issue_date: new Date().toISOString().split('T')[0],
expiry_date: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)
.toISOString().split('T')[0],
// portrait and un_distinguishing_sign are required by ISO 18013-5.1
portrait: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
un_distinguishing_sign: 'USA',
driving_privileges: [
{ vehicle_category_code: 'C', issue_date: '2020-01-01', expiry_date: '2030-01-01' },
],
Expand Down
11 changes: 10 additions & 1 deletion oid4vc/demo/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,18 @@
# WALLET_URL default http://localhost:7101
set -euo pipefail

# Load .env from the same directory as this script so port overrides are honoured.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ -f "$SCRIPT_DIR/.env" ]]; then
set -a
# shellcheck disable=SC1091
. "$SCRIPT_DIR/.env"
set +a
fi

ISSUER_ADMIN="${ACAPY_ISSUER_ADMIN_URL:-http://localhost:8021}"
VERIFIER_ADMIN="${ACAPY_VERIFIER_ADMIN_URL:-http://localhost:8031}"
WALLET_URL="${WALLET_URL:-http://localhost:7101}"
WALLET_URL="${WALTID_WALLET_URL:-${WALLET_URL:-http://localhost:7101}}"

GREEN='\033[0;32m'
YELLOW='\033[1;33m'
Expand Down
Loading
Loading