Skip to content

feat(mso_mdoc): trust anchor registry, signing key registry, and mDOC revocation#30

Merged
burdettadam merged 23 commits intomainfrom
refactor/mso-mdoc-align-patterns
Mar 24, 2026
Merged

feat(mso_mdoc): trust anchor registry, signing key registry, and mDOC revocation#30
burdettadam merged 23 commits intomainfrom
refactor/mso-mdoc-align-patterns

Conversation

@burdettadam
Copy link
Collaborator

@burdettadam burdettadam commented Mar 18, 2026

Summary

Aligns mso_mdoc with the established plugin patterns, adds a proper trust anchor and signing key registry backed by the ACA-Py store, and implements end-to-end mDOC credential revocation using the IETF Token Status List.


Changes

Trust Anchor & Signing Key Registry

  • TrustAnchorRecord (BaseRecord) stores IACA/reader-auth root CAs per doctype (or wildcard); CRUD routes at /mso-mdoc/trust-anchors
  • MdocSigningKeyRecord (BaseRecord) stores issuer private key + certificate per doctype; CRUD routes at /mso-mdoc/signing-keys
  • _get_trust_anchors(profile, doctype=None) queries TrustAnchorRecord first, falls back to SupportedCredential.vc_additional_data
  • _resolve_signing_key(supported, profile) queries MdocSigningKeyRecord first, falls back to vc_additional_data legacy fields

mDOC Revocation (IETF Token Status List)

  • _assign_status_entry() at issuance calls the status_list plugin to allocate a slot and embeds {status: {status_list: {idx, uri}}} in the MSO payload
  • check_status_list_claim(claims) at verification: HTTP GET the published status list JWT, base64url-decode + zlib-decompress the lst field, check little-endian bit at position idx; fail-open on network/parse errors
  • Wired into both pres_verifier.py (OID4VP path) and cred_verifier.py (direct verification path)

Refactor (original scope)

  • Removed 10 excess admin endpoints, custom storage subsystem, and standalone key/trust-anchor management
  • Aligned mso_mdoc with sd_jwt_vc registration pattern
  • Deleted: storage/ package, key_routes.py, trust_anchor_routes.py, key_generation.py, mdoc/trust_store.py, 11 obsolete test files

Bug Fixes

  • _get_trust_anchors(doctype=None) now returns all trust anchors (previously only returned wildcard records, silently missing doctype-specific ones)
  • setup_credo_trust_anchors fixture now uploads root_ca_pem instead of the leaf DS cert (Credo validates the full chain)

Tests

  • 97 unit tests, 0 failures
  • 15 new tests for check_status_list_claim (valid, revoked, 2-bit entries, fail-open, edge cases)
  • 9 new processor method tests (_get_trust_anchors, _resolve_signing_key, _assign_status_entry)
  • Fixed test isolation: replaced shared module-scoped profile with fresh per-test profiles where records accumulate across test classes

All 97 unit tests pass.

…0 excess admin endpoints, custom storage subsystem, key/trust-anchor mgmt. Align with sd_jwt_vc pattern: 2 admin routes, signing keys in vc_additional_data, trust anchors in vc_additional_data.trust_anchors

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
…gn-patterns

# Conflicts:
#	oid4vc/mso_mdoc/key_generation.py
#	oid4vc/mso_mdoc/key_routes.py
#	oid4vc/mso_mdoc/storage/__init__.py
… upload

- Move cryptography imports to module level in test_credo_mdoc.py and
  cred_processor.py (PLC0415)
- Fix unsorted import block and use datetime.UTC alias in test_credo_mdoc.py
  (I001, UP017)
- Break long lines in cred_verifier.py and pres_verifier.py (E501)
- Include required fields (format, id, doctype) in PUT body when uploading
  trust anchors via setup_acapy.py to fix 422 from MsoMdocSupportedCredCreateReq

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
… UP017)

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
Signed-off-by: Adam Burdett <burdettadam@gmail.com>
- Add MsoMdocSupportedCredUpdateReq with all fields optional so that
  partial updates (e.g. only trust_anchors, or only signing_key_pem)
  work without requiring format/id/doctype in the request body.
- Rewrite supported_cred_update_helper to merge with existing record
  values instead of overwriting with None for missing fields.
- Switch @request_schema on PUT route to MsoMdocSupportedCredUpdateReq.
- Fix test_mdoc_issuer_key_generation to assert private_key_pem and
  certificate_pem (what the fixture actually returns) instead of the
  old key_id/verification_method fields that no longer exist.

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
…credential configs

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
Signed-off-by: Adam Burdett <burdettadam@gmail.com>
@burdettadam burdettadam changed the title refactor(mso_mdoc): align with established plugin patterns feat(mso_mdoc): trust anchor registry, signing key registry, and mDOC revocation Mar 19, 2026
…mDOC revocation infrastructure

- Add TrustAnchorRecord(BaseRecord) with TAG_NAMES {doctype, purpose} for
  multi-tenant-safe X.509 trust anchor storage. Replaces cramming trust
  anchors into SupportedCredential.vc_additional_data.

- Add MdocSigningKeyRecord(BaseRecord) with TAG_NAMES {doctype, label} for
  multi-tenant-safe signing key storage. private_key_pem is stored in the
  encrypted Askar wallet and is load_only in the schema (never via API).

- Add trust_anchor_routes.py with full CRUD:
  POST/GET/GET/{id}/DELETE /mso-mdoc/trust-anchors
  POST/GET/GET/{id}/DELETE /mso-mdoc/signing-keys

- Update cred_processor._get_trust_anchors() to query TrustAnchorRecord
  first, with backward-compat fallback to SupportedCredential.vc_additional_data.

- Update cred_processor._resolve_signing_key() to check MdocSigningKeyRecord
  (by signing_key_id or doctype) before falling back to vc_additional_data
  and env vars.

- Add _assign_status_entry() for optional mDOC revocation: if the status_list
  plugin is installed and status_list_def_id + status_list_base_uri are set on
  the SupportedCredential, assigns a Token Status List entry at issuance and
  embeds {status: {status_list: {idx, uri}}} in the credential payload.

- Update SupportedCredential route schemas to accept signing_key_id,
  status_list_def_id, and status_list_base_uri fields.

- Update integration conftest.py:
  - setup_issuer_certs: generates a proper IACA-compliant leaf DS cert
    (via _generate_leaf_ds) and uploads via POST /mso-mdoc/signing-keys
    with vc_additional_data fallback for older agents.
  - setup_verifier_trust_anchors: uses POST /mso-mdoc/trust-anchors with
    vc_additional_data fallback.
  - setup_all_trust_anchors: yields issuer_key_pem (was missing, causing KeyError).
  - setup_pki_chain_trust_anchor: uses POST /mso-mdoc/trust-anchors.

- Add test_trust_registry.py with 12 unit tests covering record creation,
  serialisation, save/retrieve, query-by-tag, and delete.

Signed-off-by: Adam Burdett <adam@indicio.tech>
Signed-off-by: Adam Burdett <burdettadam@gmail.com>
- check_status_list_claim() in utils.py: IETF Token Status List HTTP
  fetch + little-endian bit decode; fail-open on network/parse errors
- pres_verifier.py: revocation check after issuer signature success
- cred_verifier.py: revocation check after issuer signature success
- _get_trust_anchors(): fix doctype=None to include ALL anchors (not
  just wildcard records)
- setup_credo_trust_anchors: upload root_ca_pem instead of leaf cert
- tests: 15 unit tests for check_status_list_claim; expanded
  test_trust_registry with 9 processor method tests; fix test isolation
  by using fresh profiles instead of shared module-scoped profile

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
@burdettadam burdettadam force-pushed the refactor/mso-mdoc-align-patterns branch from 9cb54ed to e14c0b2 Compare March 19, 2026 18:33
burdettadam and others added 13 commits March 19, 2026 12:52
Resolve modify/delete conflicts by keeping the refactor removal of the old
mso_mdoc key-management and storage subsystem. The branch intentionally
replaced that architecture, so restoring main's deleted files would reintroduce
dead code and broken imports.

Verified with:
- poetry run pytest mso_mdoc/tests/ -v --no-cov
- 97 passed

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
Signed-off-by: Adam Burdett <burdettadam@gmail.com>
… registry

The refactor replaced vc_additional_data trust_anchors with a proper
TrustAnchorRecord Askar registry accessible via POST /mso-mdoc/trust-anchors.
Update the conformance test helper to POST to the new endpoint instead of
patching each SupportedCredential record individually.

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
…t_anchors

proven-api exclusively uses the /mso-mdoc/trust-anchors registry.
No deployment writes trust anchors to SupportedCredential.vc_additional_data.
The fallback is dead code — remove it and its covering test.

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
…_signing_key

proven-api exclusively uses MdocSigningKeyRecord via POST /mso-mdoc/signing-keys.
Steps 3 (vc_additional_data signing_key_pem/signing_cert_pem) and 4 (env vars
OID4VC_MDOC_SIGNING_KEY_PATH/OID4VC_MDOC_SIGNING_CERT_PATH) are dead code.
Remove them and update tests to inject key material via MdocSigningKeyRecord.query.

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
- POST /mso-mdoc/signing-keys now generates EC P-256 key pair server-side
  and returns public_key_pem (private key never exposed via API)
- POST /mso-mdoc/signing-keys/import for pre-existing keys tied to
  public registries (IACA, etc.)
- PUT /mso-mdoc/signing-keys/{id} to attach certificate and update
  metadata, with public key match validation
- Removed dead signing_key_pem/signing_cert_pem from legacy mso_mdoc
  routes (never read by _resolve_signing_key)
- Fixed IACA BasicConstraints path_length for direct root->leaf signing
  (was 1, should be 0 when no intermediate CA)
- Updated integration test fixtures to use two-step generate+attach flow

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
- POST /mso-mdoc/signing-keys now generates EC P-256 key pair server-side
- POST /mso-mdoc/signing-keys/import for pre-existing keys (IACA registry)
- PUT /mso-mdoc/signing-keys/{id} to attach certificate after generation
- Response includes public_key_pem (derived); private_key_pem never exposed
- Remove dead signing_key_pem/signing_cert_pem from legacy mso-mdoc routes
- Fix IACA path_length for direct root->leaf cert chains in tests

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
Signed-off-by: Adam Burdett <burdettadam@gmail.com>
Signed-off-by: Adam Burdett <burdettadam@gmail.com>
All integration tests still referenced signing_key_pem/signing_cert_pem
in vc_additional_data and setup_all_trust_anchors, even though these
fields were removed from the API. Updated test_credo_mdoc.py to use
the /mso-mdoc/signing-keys/import endpoint directly.

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
…tests

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
mdoc_offer_verification_method depended on mdoc_issuer_key which creates
a bare cert not registered as a trust anchor on the ACA-Py verifier.
During presentation tests _resolve_signing_key may pick the bare-cert
record, causing the verifier to reject the mDoc. Using setup_issuer_certs
ensures the signing key is the same one whose root CA is registered on both
Credo and the ACA-Py verifier.

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
@burdettadam burdettadam merged commit 01c8c0a into main Mar 24, 2026
6 checks passed
@burdettadam burdettadam deleted the refactor/mso-mdoc-align-patterns branch March 24, 2026 22:07
burdettadam added a commit that referenced this pull request Mar 25, 2026
- Merged origin/main which includes PR #30 refactor/mso-mdoc-align-patterns
- Resolved conflict in oid4vc/mso_mdoc/routes.py:
  - Removed sign/verify endpoints (deleted upstream with key_generation,
    key_routes, and storage modules)
  - Fixed imports to use new trust_anchor_routes.register(app) pattern
  - Kept OID4VCI 1.0 improvements (credential_definition, credential_metadata,
    credential_signing_alg_values_supported, RAISE validation)
- Fixed variable name mismatch in supported_cred.py to_issuer_metadata()
- Fixed test mock return values (hex string for bytes.fromhex compatibility)
- All 292 tests passing

Signed-off-by: Adam Burdett <burdettadam@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants