Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
4167fe4
feat: Add mdoc path to demo UI Issuance
TheTechmage May 12, 2025
788b11d
feat: Send mdoc credential in demo
TheTechmage May 12, 2025
06513c8
fix: credentials being sent for mso_mdoc
TheTechmage May 16, 2025
7835350
feat(WIP): mdl tests
mepeltier May 20, 2025
ca47589
feat: update sphereon lib
mepeltier May 20, 2025
d1a029d
feat(wip): minor test additions
mepeltier May 20, 2025
cad7e7e
fix: Use randomly generated doc ID
TheTechmage May 21, 2025
322d5cc
feat: Upgrade oid4vc to draft 13 & mdl test progress
TheTechmage May 21, 2025
894c2f2
Merge remote-tracking branch 'refs/remotes/indicio/feat/mdoc' into fe…
TheTechmage May 21, 2025
cb14d0a
feat: Add isomdl based test
TheTechmage May 23, 2025
166b7f4
feat: Add forgotten isomdl library
TheTechmage May 27, 2025
220052d
feat(WIP): uniffi wrapper mso mdoc cred processor
mepeltier May 29, 2025
8b1d595
fix: oid4vc client tests and push further on isomdl tests
TheTechmage May 29, 2025
e69cce0
Merge remote-tracking branch 'refs/remotes/indicio/feat/mdoc' into fe…
TheTechmage May 29, 2025
9b2ab09
fix: isomdl wrapper volume mapping
cjhowland Sep 8, 2025
5c47e41
fix: misc. fixes for test running
mepeltier Sep 18, 2025
be7c564
feat: migrate from Poetry to UV and integrate isomdl-uniffi from git …
burdettadam Oct 30, 2025
4cac466
feat(mso_mdoc): Complete rewrite using isomdl-uniffi for ISO 18013-5 …
burdettadam Oct 31, 2025
9bfa7b0
feat: comprehensive test coverage expansion and OID4VCI 1.0 compliance
burdettadam Oct 31, 2025
7f2fdd8
feat: Enable Docker builds and core integration tests for OID4VC plugin
burdettadam Nov 4, 2025
0f2be48
Migrate compliance tests to integration suite and cleanup
burdettadam Nov 19, 2025
bccbbf5
Add integration test runner and docker configurations
burdettadam Nov 19, 2025
149b3c0
Update OID4VC integration tests and Docker config
burdettadam Nov 19, 2025
89b4217
Merge upstream main into feature branch, resolving conflicts
burdettadam Nov 20, 2025
1bec66d
fix(oid4vc): resolve post-merge issues, circular imports, and add val…
burdettadam Nov 20, 2025
c3d9320
Update mso_mdoc modules: cred_processor, issuer, and storage
burdettadam Nov 20, 2025
108f824
Code style cleanup: fix linting errors and format imports
burdettadam Nov 21, 2025
dcd3891
feature: register mdoc processor
burdettadam Nov 21, 2025
a5caf33
chore: apply pre-commit fixes and update oid4vc plugin
burdettadam Nov 26, 2025
b88cb32
check point
burdettadam Nov 26, 2025
d5ca981
fix: resolve mso_mdoc test failures and cleanup codebase
burdettadam Dec 1, 2025
2288ee5
feat(oid4vc): Add Sphereon interop testing and fix credential offer/p…
burdettadam Dec 3, 2025
eb3ad8a
oid4vc v1
burdettadam Dec 3, 2025
153db47
status list tests
burdettadam Dec 4, 2025
c91ab59
feat: update mdoc verifier to use intermediate chaining and update do…
burdettadam Dec 6, 2025
aeef12f
checkpoint: lots of goodies
burdettadam Dec 10, 2025
bd63ecf
checkpoint: failing device sig
burdettadam Dec 10, 2025
4ac24f7
feat(oid4vc): Add cross-wallet compatibility tests, fix mDOC verifica…
burdettadam Dec 11, 2025
956d478
feat(oid4vc): Add wallet-based trust anchor storage and dynamic PKI c…
burdettadam Dec 11, 2025
4e59272
test: improved trust store tests
burdettadam Dec 12, 2025
b31b8fd
feat: add DCQL support and refactor routes
burdettadam Dec 12, 2025
4c23811
feat: address todos in tests
burdettadam Dec 12, 2025
39762e5
feat: some more tests
burdettadam Dec 13, 2025
e246a93
fix: passing tests
burdettadam Dec 15, 2025
1abf0e9
feat: improved e2e tests
burdettadam Dec 15, 2025
7f56f65
fix: change playwright config
burdettadam Dec 15, 2025
c6a9ba9
fix: code review improvements for mso_mdoc plugin
burdettadam Dec 19, 2025
0359404
fix: requested fixes
burdettadam Dec 19, 2025
eb9cbdb
fix: address feedback
burdettadam Dec 19, 2025
a0292a3
fix: pr feedback
burdettadam Dec 19, 2025
9f9012c
fix: requested changes
burdettadam Dec 19, 2025
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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ Temporary Items
.idea/*
**/.idea/*

###
### Visual Studio Code
###

.vscode/
*.code-workspace

###
### Windows
###
Expand Down
49 changes: 49 additions & 0 deletions oid4vc/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
.venv/
venv/
ENV/
.pytest_cache/
.mypy_cache/
.ruff_cache/
*.egg-info/
dist/
build/
.eggs/

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# Test outputs
.test-reports/
test_results/
test_data/
htmlcov/
.coverage
coverage.xml

# Development
.dev/
.devcontainer/
demo/
devtools/
docs/

# Git
.git/
.gitignore

# Docker (don't need to copy these into image)
docker-compose*.yml
Dockerfile*

# Pre-commit
.pre-commit-config.yaml
45 changes: 45 additions & 0 deletions oid4vc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Local development tools - do not commit
.dev/

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# UV
.venv/
uv.lock

# UniFFI generated bindings (now use GitHub package)
**/uniffi_scratch/

# IDE
.vscode/
.idea/
*.code-workspace
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Test Results
test-results/
2 changes: 1 addition & 1 deletion oid4vc/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ repos:
stages: [commit]
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.285
rev: v0.4.4
hooks:
- id: ruff
stages: [commit]
Expand Down
21 changes: 10 additions & 11 deletions oid4vc/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# 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.

## OpenID4VCI Plugin Demo with Sphereon Wallet
## OpenID4VCI Plugin Demo

### Demo Overview

This repository showcases a simplified demonstration of the OID4VCI (OpenID for Verifiable Credential Issuers) integration with the [Sphereon Wallet app](https://github.com/Sphereon-Opensource/ssi-mobile-wallet). Follow the steps below to run the demo successfully.
This repository showcases a demonstration of the OID4VCI (OpenID for Verifiable Credential Issuers) integration using ACA-Py as both issuer and verifier, with Credo as the holder agent.

### Prerequisites

- Sphereon Wallet App on your mobile device
- Docker + Docker Compose
- Ngrok Account (free tier is okay)

Expand Down Expand Up @@ -48,17 +47,17 @@ Navigate to `http://localhost:3002` in your browser. You will start at the landi

2. Credential Offer Page
- Presents a credential offer in the form of a QR code.
- Scan the QR code using the Sphereon Wallet app.
- The Sphereon Wallet follows the OID4VC flow, requesting an authentication token and using it to obtain a credential.
- Scan the QR code using a compatible wallet app.
- The wallet follows the OID4VC flow, requesting an authentication token and using it to obtain a credential.
- The OID4VC plugin determines the credential subjects based on the exchange record.

Now you have a `UniversityCredential` in your Sphereon Wallet. To demonstrate the other half of the OID4VC plugin, click on the `Present Credential` button on the sidebar.
Now you have a `UniversityCredential` in your wallet. To demonstrate the other half of the OID4VC plugin, click on the `Present Credential` button on the sidebar.

3. Present Credential
- The Present Credential page has a single button on it: Present Credential
- When you press that button, the demo will prepare a QR code that contains a presentation request
- Again, the demo obscures and automates some of the necessary calls to prepare the request, but you can see the calls being made in the logs
- Scan this QR code with your Sphereon Wallet app
- Scan this QR code with your wallet app
- Follow the steps on the app, which will prompt you to select a University Credential from your wallet

As mentioned, the demo automatically takes care of a lot of the setup calls necessary to prepare credential definitions, presentation requests, and so forth. You can see what calls are being made, and with what values, both in the container logs and on the page.
Expand Down Expand Up @@ -385,10 +384,10 @@ poetry run pytest tests/

### Integration Tests

This plugin includes two sets of integration tests:
This plugin includes integration tests:

- Tests against a minimal OpenID4VCI Client written in Python
- Interop Tests against Credo and Sphereon
- Interop Tests against Credo
- The interop tests require an https endpoint, so they aren't run with the regular integration tests. See `integration/README.md` for instructions on running the interop tests

To run the integration tests:
Expand All @@ -411,4 +410,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
1 change: 0 additions & 1 deletion oid4vc/auth_server/admin/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@

from core.db.session import DatabaseSessionManager, make_session_dependency


db_manager = DatabaseSessionManager(search_path="admin")
get_db_session = make_session_dependency(db_manager)
9 changes: 4 additions & 5 deletions oid4vc/auth_server/admin/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
from contextlib import asynccontextmanager
from typing import AsyncIterator

from fastapi import FastAPI, Request, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import ORJSONResponse
from sqlalchemy import text

from admin.config import settings
from admin.deps import db_manager
from admin.routers import internal, migrations, tenants
Expand All @@ -16,6 +11,10 @@
setup_structlog_json,
)
from core.utils.logging import get_logger
from fastapi import FastAPI, Request, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import ORJSONResponse
from sqlalchemy import text

logger = get_logger(__name__)

Expand Down
5 changes: 2 additions & 3 deletions oid4vc/auth_server/admin/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

from datetime import datetime

from admin.config import settings
from core.models import Base
from sqlalchemy import BigInteger, Boolean, ForeignKey, Text, UniqueConstraint, func
from sqlalchemy.dialects.postgresql import JSONB, TIMESTAMP
from sqlalchemy.orm import Mapped, mapped_column

from core.models import Base
from admin.config import settings


class Tenant(Base):
"""Tenant model."""
Expand Down
5 changes: 2 additions & 3 deletions oid4vc/auth_server/admin/repositories/client_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

from typing import Sequence

from sqlalchemy import delete, select, update
from sqlalchemy.ext.asyncio import AsyncSession

from core.models import Client
from core.repositories.client_repository import ClientRepository as BaseClientRepository
from sqlalchemy import delete, select, update
from sqlalchemy.ext.asyncio import AsyncSession


class ClientRepository(BaseClientRepository):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"""Data-access layer for tenant keys."""

from sqlalchemy.ext.asyncio import AsyncSession

from admin.models import TenantKey
from sqlalchemy.ext.asyncio import AsyncSession


class TenantKeyRepository:
Expand Down
4 changes: 2 additions & 2 deletions oid4vc/auth_server/admin/repositories/tenant_repository.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Data-access layer for tenants."""

from typing import Sequence
from sqlalchemy import select, update, delete
from sqlalchemy.ext.asyncio import AsyncSession

from admin.models import Tenant
from sqlalchemy import delete, select, update
from sqlalchemy.ext.asyncio import AsyncSession


class TenantRepository:
Expand Down
9 changes: 5 additions & 4 deletions oid4vc/auth_server/admin/routers/internal.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
"""API for tenant SERVICE helpers: DB info, JWKS, JWT signing."""

from fastapi import APIRouter, Depends, Path
from sqlalchemy.ext.asyncio import AsyncSession

from admin.deps import get_db_session
from admin.schemas.internal import (
JwtSignRequest,
Expand All @@ -13,8 +10,12 @@
from admin.security.bearer import require_interal_auth
from admin.services.internal_service import get_tenant_db, get_tenant_jwks
from admin.services.signing_service import sign_tenant_jwt
from fastapi import APIRouter, Depends, Path
from sqlalchemy.ext.asyncio import AsyncSession

router = APIRouter(prefix="/tenants/{uid}", dependencies=[Depends(require_interal_auth)])
router = APIRouter(
prefix="/tenants/{uid}", dependencies=[Depends(require_interal_auth)]
)


@router.get("/db", response_model=TenantDbResponse)
Expand Down
7 changes: 3 additions & 4 deletions oid4vc/auth_server/admin/routers/migrations.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
"""Router for tenant migrations."""

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession

from admin.deps import get_db_session
from admin.security.bearer import require_admin_auth
from admin.repositories.tenant_repository import TenantRepository
from admin.schemas.migration import MigrationAction, MigrationRequest
from admin.security.bearer import require_admin_auth
from admin.services.alembic_service import run_tenant_migration
from admin.utils.db_utils import resolve_tenant_urls
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession

router = APIRouter(dependencies=[Depends(require_admin_auth)])

Expand Down
5 changes: 2 additions & 3 deletions oid4vc/auth_server/admin/routers/tenants.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
"""Admin API for tenant management."""

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession

from admin.deps import get_db_session
from admin.schemas.client import ClientIn, ClientOut
from admin.schemas.tenant import KeyGenIn, KeyStatusIn, TenantIn, TenantOut
from admin.security.bearer import require_admin_auth
from admin.services.internal_service import get_tenant_jwks
from admin.services.tenant_service import TenantService
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession

router = APIRouter(dependencies=[Depends(require_admin_auth)])

Expand Down
1 change: 1 addition & 0 deletions oid4vc/auth_server/admin/schemas/internal.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Schemas for signing JWTs for tenants."""

from typing import Literal

from pydantic import BaseModel


Expand Down
1 change: 1 addition & 0 deletions oid4vc/auth_server/admin/schemas/migration.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Schemas for migrations."""

from enum import Enum

from pydantic import BaseModel


Expand Down
3 changes: 2 additions & 1 deletion oid4vc/auth_server/admin/schemas/tenant.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Schemas for tenants."""

from datetime import datetime
from pydantic import BaseModel, Field, ConfigDict

from pydantic import BaseModel, ConfigDict, Field


class TenantIn(BaseModel):
Expand Down
4 changes: 1 addition & 3 deletions oid4vc/auth_server/admin/security/bearer.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"""Bearer auth dependencies for Admin API (router-level guards)."""

from admin.config import settings
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

from admin.config import settings


_security = HTTPBearer(auto_error=False)


Expand Down
9 changes: 5 additions & 4 deletions oid4vc/auth_server/admin/services/client_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

import uuid

from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession

from admin.repositories.client_repository import ClientRepository
from admin.schemas.client import ClientIn
from core.consts import CLIENT_AUTH_METHODS, ClientAuthMethod
from core.crypto.crypto import hash_secret_pbkdf2
from core.models import Client
from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession


class ClientService:
Expand Down Expand Up @@ -86,7 +85,9 @@ async def update(self, client_id: str, data: ClientIn) -> int:
if not row:
raise HTTPException(status_code=404, detail="client_not_found")
values = {
k: v for k, v in data.model_dump(exclude_unset=True).items() if v is not None
k: v
for k, v in data.model_dump(exclude_unset=True).items()
if v is not None
}
changed = await self.repo.update_values(row.id, values)
await self.session.commit()
Expand Down
Loading