Skip to content

Commit 66e6c86

Browse files
weiivtimbl-ontTim Bloomfieldburdettadam
authored
Follow-on updates for PR#2871 (#3032)
* fix the demo to support oid4vci 1.0 * move DID creation to initialization section * Fix for mdoc demo * added status update and refresh to the oid4vc demo * fixed JWT type in demo * add more plugins to the block list * change DB version and minor updates * feat(oid4vc): mDoc support, OID4VCI 1.0 compliance, DCQL, and route refactoring This PR brings the Indicio-tech fork changes into upstream as a single squashed commit with a clean git history. ## Summary - mDoc / mso_mdoc: Full mDoc credential format support using isomdl-uniffi, trust anchor registry, signing key management, status list revocation, and presentation verification - OID4VCI 1.0 Compliance: Token endpoint updated to final spec + HAIP profile, DPoP support, backward compatibility for draft clients, OAuth discovery endpoint - OID4VP / Verification: OID4VP Final with x5c key binding, JAR fixes, did:jwk client_id, UUID4 presentation definition IDs - DCQL: Expanded query language support with multi-credential flows - Routes Refactoring: Split monolithic public_routes.py and routes.py into focused submodules - SD-JWT VC and JWT VC JSON: Selective disclosure fixes, OID4VCI 1.0 pattern alignment - status_list: Endianness fix Note: Demo, integration tests, unit tests, CI workflows, docker files, poetry.lock files, and changes to non-oid4vc plugins are excluded from this PR and will be submitted separately. Signed-off-by: Adam Burdett <burdettadam@gmail.com> * fixed ngrok health check in docker-compose * fix sd-jwt issuance to credo * fixed liveness issues on ngrok * indicio's mdoc pr + ontario's oid4vci 1.0 pr Signed-off-by: Ivan Wei <ivan.wei@ontario.ca> * build rust mdoc library in docker * temp fix for mdoc status list check * add verification for mso_mdoc using DCQL * fixed demo cert to be proper IACA Cert * Third-Party DCO Remediation Commit for Tim Bloomfield <tim.bloomfield@ontario.ca> On behalf of Tim Bloomfield <tim.bloomfield@ontario.ca>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: 507b7e2 On behalf of Tim Bloomfield <tim.bloomfield@ontario.ca>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: f6e08f9 On behalf of Tim Bloomfield <tim.bloomfield@ontario.ca>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: 7eb105c Signed-off-by: Ivan Wei <ivan.wei@ontario.ca> * Third-Party DCO Remediation Commit for Tim Bloomfield <tim.bloomfield@iccs-isac.org> On behalf of Tim Bloomfield <tim.bloomfield@iccs-isac.org>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: f0eef22 Signed-off-by: Ivan Wei <ivan.wei@ontario.ca> * Third-Party DCO Remediation Commit for Tim Bloomfield <tim.bloomfield@ontario.ca> On behalf of Tim Bloomfield <tim.bloomfield@ontario.ca>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: af95292 On behalf of Tim Bloomfield <tim.bloomfield@ontario.ca>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: ce9e7a0 On behalf of Tim Bloomfield <tim.bloomfield@ontario.ca>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: f48a0b7 On behalf of Tim Bloomfield <tim.bloomfield@ontario.ca>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: e0cffc8 On behalf of Tim Bloomfield <tim.bloomfield@ontario.ca>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: 0ef9dfa On behalf of Tim Bloomfield <tim.bloomfield@ontario.ca>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: cbe3e83 On behalf of Tim Bloomfield <tim.bloomfield@ontario.ca>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: a4aefc3 On behalf of Tim Bloomfield <tim.bloomfield@ontario.ca>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: 0f4fb4f Signed-off-by: Ivan Wei <ivan.wei@ontario.ca> * Third-Party DCO Remediation Commit for Tim Bloomfield <tim.bloomfield@iccs-isac.org> On behalf of Tim Bloomfield <tim.bloomfield@iccs-isac.org>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: 7ce9270 On behalf of Tim Bloomfield <tim.bloomfield@iccs-isac.org>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: 09f42ce Signed-off-by: Ivan Wei <ivan.wei@ontario.ca> * DCO Remediation for merge commit On behalf of Ivan Wei <ivan.wei@ontario.ca>, adding sign-off to commit: e398986 Signed-off-by: Ivan Wei <ivan.wei@ontario.ca> * Third-Party DCO Remediation Commit for Tim Bloomfield <tim.bloomfield@iccs-isac.org> On behalf of Tim Bloomfield <tim.bloomfield@iccs-isac.org>, I, Ivan Wei <ivan.wei@ontario.ca>, hereby add my Signed-off-by to this commit: 8fedbe2 Signed-off-by: Ivan Wei <ivan.wei@ontario.ca> * DCO Remediation Commit for Ivan Wei <ivan.wei@ontario.ca> Signed-off-by: Ivan Wei <ivan.wei@ontario.ca> * fix: linting Signed-off-by: Ivan Wei <ivan.wei@ontario.ca> * ci: install isomdl-uniffi wheel for oid4vc unit tests Signed-off-by: Ivan Wei <ivan.wei@ontario.ca> * docs: update README with per-tenant auth server config and isomdl-uniffi install instructions Signed-off-by: Ivan Wei <ivan.wei@ontario.ca> --------- Signed-off-by: Adam Burdett <burdettadam@gmail.com> Signed-off-by: Ivan Wei <ivan.wei@ontario.ca> Co-authored-by: Tim Bloomfield <tim.bloomfield@ontario.ca> Co-authored-by: Tim Bloomfield <tim.bloomfield@iccs-isac.org> Co-authored-by: Adam Burdett <burdettadam@gmail.com>
1 parent 6e6c70f commit 66e6c86

70 files changed

Lines changed: 2066 additions & 1220 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/pr-linting-and-unit-tests.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ jobs:
7373
cd $dir
7474
echo "Installing dependencies for $dir"
7575
poetry install --no-interaction --no-root --all-extras
76+
# Install isomdl-uniffi from pre-built wheel (not on PyPI)
77+
if [ "$dir" = "oid4vc" ]; then
78+
poetry run pip install https://github.com/Indicio-tech/isomdl-uniffi/releases/download/v0.1.0-indicio.1/isomdl_uniffi-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
79+
fi
7680
cd ..
7781
done
7882
#----------------------------------------------

oid4vc/README.md

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,33 @@ The Plugin expects the following configuration options. These options can either
255255
- `credential_issuer` endpoint, seen in the Credential Offer
256256
- `OID4VCI_CRED_HANDLER` or `oid4vci.cred_handler`
257257
- Dict of credential handlers. e.g. `{"jwt_vc_json": "jwt_vc_json"}`
258-
- `OID4VCI_AUTH_SERVER_URL` or `oid4vci.auth_server_url`
259-
- Optional authorization server URL
260-
- `OID4VCI_AUTH_SERVER_CLIENT` or `oid4vci.auth_server_client`
261-
- Optional authorization server client credential, e.g. `{"auth_type": "client_secret_basic", "client_id": "client_id", "client_secret": "client_secret"}`
258+
259+
#### Authorization Server (Per-Tenant)
260+
261+
Authorization server configuration is managed per-tenant via the `IssuerConfiguration` record, not through global environment variables. Use the admin API:
262+
263+
- `PUT /oid4vci/issuer/configuration` — create or update the issuer configuration
264+
- `GET /oid4vci/issuer/configuration` — retrieve the current configuration
265+
266+
Example payload to configure an external authorization server:
267+
268+
```json
269+
{
270+
"authorization_servers": [
271+
{
272+
"public_url": "https://auth.example.com/tenant/abc123",
273+
"private_url": "https://auth-internal:8080/tenant/abc123",
274+
"auth_type": "client_secret_basic",
275+
"client_credentials": {
276+
"client_id": "issuer-client",
277+
"client_secret": "secret"
278+
}
279+
}
280+
]
281+
}
282+
```
283+
284+
Supported `auth_type` values: `client_secret_basic`, `client_secret_jwt`, `private_key_jwt`.
262285

263286
### Creating Supported Credential Records
264287

@@ -426,6 +449,29 @@ docker compose down -v # Clean up
426449

427450
For Apple Silicon, the `DOCKER_DEFAULT_PLATFORM=linux/amd64` environment variable will be required.
428451

452+
## Development Setup
453+
454+
After cloning the repo and installing dependencies with `poetry install --all-extras`, you must install the `isomdl-uniffi` package separately. It provides the Rust-based ISO 18013-5 mDoc signing bindings and is not on PyPI — only pre-built wheels are available from GitHub releases.
455+
456+
Pick the wheel for your platform:
457+
458+
**macOS (Apple Silicon):**
459+
```bash
460+
poetry run pip install https://github.com/Indicio-tech/isomdl-uniffi/releases/download/v0.1.0-indicio.1/isomdl_uniffi-0.1.0-py3-none-macosx_11_0_arm64.whl
461+
```
462+
463+
**macOS (Intel):**
464+
```bash
465+
poetry run pip install https://github.com/Indicio-tech/isomdl-uniffi/releases/download/v0.1.0-indicio.1/isomdl_uniffi-0.1.0-py3-none-macosx_10_12_x86_64.whl
466+
```
467+
468+
**Linux (x86_64):**
469+
```bash
470+
poetry run pip install https://github.com/Indicio-tech/isomdl-uniffi/releases/download/v0.1.0-indicio.1/isomdl_uniffi-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
471+
```
472+
473+
Without this, importing `mso_mdoc` will fail with `ModuleNotFoundError: No module named 'isomdl_uniffi'`.
474+
429475
## Not Implemented
430476

431477
- `ldp_vc`

oid4vc/auth_server/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ ADMIN_INTERNAL_AUTH_TOKEN=admin-internal-token
4646

4747
```
4848
TENANT_ISSUER_BASE_URL=http://localhost:9001
49-
TENANT_ADMIN_INTERNAL_BASE_URL=http://localhost:9000/internal
50-
TENANT_ADMIN_INTERNAL_AUTH_TOKEN=admin-internal-token
49+
TENANT_INTERNAL_BASE_URL=http://localhost:9000/internal
50+
TENANT_INTERNAL_AUTH_TOKEN=admin-internal-token
5151
```
5252

5353
## ✅ Health Checks

oid4vc/auth_server/admin/routers/internal.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
TenantDbResponse,
1111
TenantJwksResponse,
1212
)
13-
from admin.security.bearer import require_interal_auth
13+
from admin.security.bearer import require_internal_auth
1414
from admin.services.internal_service import get_tenant_db, get_tenant_jwks
1515
from admin.services.signing_service import sign_tenant_jwt
1616

17-
router = APIRouter(prefix="/tenants/{uid}", dependencies=[Depends(require_interal_auth)])
17+
router = APIRouter(prefix="/tenants/{uid}", dependencies=[Depends(require_internal_auth)])
1818

1919

2020
@router.get("/db", response_model=TenantDbResponse)

oid4vc/auth_server/admin/schemas/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ class ClientIn(BaseModel):
1313
default=None,
1414
)
1515
client_auth_method: str | None = Field(
16-
description="Auth method: client_secret_basic | shared_key_jwt | private_key_jwt",
16+
description="Auth method: client_secret_basic|client_secret_jwt|private_key_jwt",
1717
default=None,
1818
)
1919
client_auth_signing_alg: str | None = Field(
2020
description="e.g., ES256 or HS256",
2121
default=None,
2222
)
2323
client_secret: str | None = Field(
24-
description="For client_secret_basic or shared_key_jwt",
24+
description="For client_secret_basic or client_secret_jwt",
2525
default=None,
2626
)
2727
jwks: dict[str, Any] | None = Field(

oid4vc/auth_server/admin/security/bearer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
_security = HTTPBearer(auto_error=False)
1010

1111

12-
def require_interal_auth(
12+
def require_internal_auth(
1313
credentials: HTTPAuthorizationCredentials | None = Depends(_security),
1414
) -> bool:
1515
"""Validate internal routes via Bearer from settings."""

oid4vc/auth_server/admin/services/client_service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ async def create(self, data: ClientIn) -> Client:
3333
if not signing_alg:
3434
if method == ClientAuthMethod.PRIVATE_KEY_JWT:
3535
signing_alg = "ES256"
36-
elif method == ClientAuthMethod.SHARED_KEY_JWT:
36+
elif method == ClientAuthMethod.CLIENT_SECRET_JWT:
3737
signing_alg = "HS256"
3838

3939
# Validate fields by method
@@ -42,7 +42,7 @@ async def create(self, data: ClientIn) -> Client:
4242
if not (data.jwks or data.jwks_uri):
4343
raise HTTPException(status_code=400, detail="jwks_or_uri_required")
4444
elif method in (
45-
ClientAuthMethod.SHARED_KEY_JWT,
45+
ClientAuthMethod.CLIENT_SECRET_JWT,
4646
ClientAuthMethod.CLIENT_SECRET_BASIC,
4747
):
4848
if not data.client_secret:
Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
1-
--
2-
-- create auth_server_admin user
3-
--
4-
CREATE ROLE auth_server_admin
1+
-- create :ADMIN_DB_USER user
2+
CREATE ROLE :ADMIN_DB_USER
53
WITH
6-
LOGIN PASSWORD 'auth_server_admin' NOSUPERUSER CREATEDB CREATEROLE INHERIT NOBYPASSRLS NOREPLICATION;
4+
LOGIN PASSWORD :ADMIN_DB_PASSWORD NOSUPERUSER CREATEDB CREATEROLE INHERIT NOBYPASSRLS NOREPLICATION;
75

8-
--
9-
-- create auth_server_admin database
10-
--
11-
CREATE DATABASE auth_server_admin OWNER auth_server_admin ENCODING 'UTF8' TEMPLATE template0;
6+
-- create :ADMIN_DB_NAME database
7+
CREATE DATABASE :ADMIN_DB_NAME OWNER :ADMIN_DB_USER ENCODING 'UTF8' TEMPLATE template0;
128

13-
--
14-
-- grant permissions to auth_server_admin
15-
--
9+
-- grant permissions to :ADMIN_DB_USER
1610
GRANT CONNECT,
17-
CREATE ON DATABASE auth_server_admin TO auth_server_admin;
11+
CREATE ON DATABASE :ADMIN_DB_NAME TO :ADMIN_DB_USER;

oid4vc/auth_server/core/consts.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ class ClientAuthMethod:
2020

2121
CLIENT_SECRET_BASIC = "client_secret_basic"
2222
PRIVATE_KEY_JWT = "private_key_jwt"
23-
SHARED_KEY_JWT = "shared_bearer"
23+
CLIENT_SECRET_JWT = "client_secret_jwt"
2424

2525

2626
CLIENT_AUTH_METHODS: tuple[str, ...] = (
2727
ClientAuthMethod.CLIENT_SECRET_BASIC,
2828
ClientAuthMethod.PRIVATE_KEY_JWT,
29-
ClientAuthMethod.SHARED_KEY_JWT,
29+
ClientAuthMethod.CLIENT_SECRET_JWT,
3030
)

oid4vc/auth_server/core/security/client_auth.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,10 @@ async def _authenticate_private_key_jwt(
109109
)
110110

111111

112-
async def _authenticate_shared_key_jwt(
112+
async def _authenticate_client_secret_jwt(
113113
client: AuthClient, token: str, request: Request, presented_client_id: str
114114
) -> Mapping[str, Any]:
115-
"""Validate shared_key_jwt assertions signed with a shared secret."""
115+
"""Validate client_secret_jwt assertions signed with a shared secret."""
116116

117117
secret = client.client_secret or ""
118118
if not secret:
@@ -191,7 +191,8 @@ async def base_client_auth(
191191
if allowed == CLIENT_AUTH_METHOD.CLIENT_SECRET_BASIC and scheme != "basic":
192192
raise HTTPException(status_code=401, detail="unauthorized_client")
193193
if (
194-
allowed in {CLIENT_AUTH_METHOD.PRIVATE_KEY_JWT, CLIENT_AUTH_METHOD.SHARED_KEY_JWT}
194+
allowed
195+
in {CLIENT_AUTH_METHOD.PRIVATE_KEY_JWT, CLIENT_AUTH_METHOD.CLIENT_SECRET_JWT}
195196
and scheme != "bearer"
196197
):
197198
raise HTTPException(status_code=401, detail="unauthorized_client")
@@ -201,8 +202,8 @@ async def base_client_auth(
201202
request.state.client_id = str(client.client_id)
202203
return client
203204

204-
if allowed == CLIENT_AUTH_METHOD.SHARED_KEY_JWT:
205-
await _authenticate_shared_key_jwt(client, token, request, str(client_id))
205+
if allowed == CLIENT_AUTH_METHOD.CLIENT_SECRET_JWT:
206+
await _authenticate_client_secret_jwt(client, token, request, str(client_id))
206207
request.state.client_id = str(client.client_id)
207208
return client
208209

0 commit comments

Comments
 (0)