@verana-labs/vs-agent-plugin-openid4vc 1.12.0-oidc4vc.6 → 2.0.0-dev.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +127 -157
  2. package/build/config.d.ts +12 -3
  3. package/build/config.js +115 -237
  4. package/build/config.js.map +1 -1
  5. package/build/index.d.ts +19 -12
  6. package/build/index.js +36 -16
  7. package/build/index.js.map +1 -1
  8. package/build/nestjs/OpenId4VcPlugin.js +18 -43
  9. package/build/nestjs/OpenId4VcPlugin.js.map +1 -1
  10. package/build/nestjs/V2OpenId4VcCredentialExchangesController.d.ts +19 -0
  11. package/build/nestjs/V2OpenId4VcCredentialExchangesController.js +161 -0
  12. package/build/nestjs/V2OpenId4VcCredentialExchangesController.js.map +1 -0
  13. package/build/nestjs/V2OpenId4VcPresentationsController.d.ts +19 -0
  14. package/build/nestjs/V2OpenId4VcPresentationsController.js +147 -0
  15. package/build/nestjs/V2OpenId4VcPresentationsController.js.map +1 -0
  16. package/build/nestjs/V2OpenId4VcSigningCertificatesController.d.ts +17 -0
  17. package/build/nestjs/V2OpenId4VcSigningCertificatesController.js +58 -0
  18. package/build/nestjs/V2OpenId4VcSigningCertificatesController.js.map +1 -0
  19. package/build/nestjs/dto/credential-exchange.dto.d.ts +31 -0
  20. package/build/nestjs/dto/credential-exchange.dto.js +150 -0
  21. package/build/nestjs/dto/credential-exchange.dto.js.map +1 -0
  22. package/build/nestjs/dto/index.d.ts +3 -0
  23. package/build/nestjs/dto/index.js +20 -0
  24. package/build/nestjs/dto/index.js.map +1 -0
  25. package/build/nestjs/dto/presentation.dto.d.ts +56 -0
  26. package/build/nestjs/dto/presentation.dto.js +234 -0
  27. package/build/nestjs/dto/presentation.dto.js.map +1 -0
  28. package/build/nestjs/dto/signing-certificate.dto.d.ts +8 -0
  29. package/build/nestjs/dto/signing-certificate.dto.js +40 -0
  30. package/build/nestjs/dto/signing-certificate.dto.js.map +1 -0
  31. package/build/nestjs/mappers.d.ts +5 -0
  32. package/build/nestjs/mappers.js +31 -0
  33. package/build/nestjs/mappers.js.map +1 -0
  34. package/build/sdk/setupOpenId4Vc.d.ts +8 -12
  35. package/build/sdk/setupOpenId4Vc.js +94 -80
  36. package/build/sdk/setupOpenId4Vc.js.map +1 -1
  37. package/build/services/CertificateService.d.ts +6 -22
  38. package/build/services/CertificateService.js +104 -71
  39. package/build/services/CertificateService.js.map +1 -1
  40. package/build/services/IssuerService.d.ts +31 -44
  41. package/build/services/IssuerService.js +133 -107
  42. package/build/services/IssuerService.js.map +1 -1
  43. package/build/services/VerifierService.d.ts +37 -44
  44. package/build/services/VerifierService.js +150 -226
  45. package/build/services/VerifierService.js.map +1 -1
  46. package/build/services/presentationRequest.d.ts +51 -0
  47. package/build/services/presentationRequest.js +67 -0
  48. package/build/services/presentationRequest.js.map +1 -0
  49. package/build/services/presentationVerification.d.ts +12 -0
  50. package/build/services/presentationVerification.js +13 -0
  51. package/build/services/presentationVerification.js.map +1 -0
  52. package/build/trust/CertificateTrust.d.ts +1 -4
  53. package/build/trust/CertificateTrust.js +10 -15
  54. package/build/trust/CertificateTrust.js.map +1 -1
  55. package/build/trust/keyBinding.d.ts +4 -17
  56. package/build/trust/keyBinding.js +34 -144
  57. package/build/trust/keyBinding.js.map +1 -1
  58. package/build/trust/types.d.ts +5 -16
  59. package/build/trust/types.js +8 -0
  60. package/build/trust/types.js.map +1 -1
  61. package/build/types.d.ts +21 -51
  62. package/build/types.js +3 -0
  63. package/build/types.js.map +1 -1
  64. package/build/utils/isRecord.d.ts +1 -0
  65. package/build/utils/isRecord.js +7 -0
  66. package/build/utils/isRecord.js.map +1 -0
  67. package/build/utils/serviceDisplay.d.ts +8 -0
  68. package/build/utils/serviceDisplay.js +11 -0
  69. package/build/utils/serviceDisplay.js.map +1 -0
  70. package/package.json +10 -8
  71. package/build/nestjs/CertificatesController.d.ts +0 -11
  72. package/build/nestjs/CertificatesController.js +0 -58
  73. package/build/nestjs/CertificatesController.js.map +0 -1
  74. package/build/nestjs/IssuerController.d.ts +0 -8
  75. package/build/nestjs/IssuerController.js +0 -66
  76. package/build/nestjs/IssuerController.js.map +0 -1
  77. package/build/nestjs/VerifierController.d.ts +0 -8
  78. package/build/nestjs/VerifierController.js +0 -66
  79. package/build/nestjs/VerifierController.js.map +0 -1
  80. package/build/nestjs/dto.d.ts +0 -7
  81. package/build/nestjs/dto.js +0 -34
  82. package/build/nestjs/dto.js.map +0 -1
  83. package/build/services/StatusListService.d.ts +0 -42
  84. package/build/services/StatusListService.js +0 -132
  85. package/build/services/StatusListService.js.map +0 -1
  86. package/build/trust/TrustClient.d.ts +0 -12
  87. package/build/trust/TrustClient.js +0 -120
  88. package/build/trust/TrustClient.js.map +0 -1
  89. package/build/trust/verdict.d.ts +0 -2
  90. package/build/trust/verdict.js +0 -13
  91. package/build/trust/verdict.js.map +0 -1
package/README.md CHANGED
@@ -1,194 +1,164 @@
1
- # OpenID4VC plugin
1
+ # OpenID4VC
2
2
 
3
- `@verana-labs/vs-agent-plugin-openid4vc` adds an optional OpenID4VCI issuer and OpenID4VP verifier to VS Agent.
3
+ `@verana-labs/vs-agent-plugin-openid4vc` gives VS Agent an OpenID4VCI issuer and an OpenID4VP
4
+ verifier for `dc+sd-jwt` credentials. It ships in every `vs-agent` image and turns on when the
5
+ operator sets `OID4VC_CONFIG_FILE_LOCATION`. Nothing else enables it.
4
6
 
5
- The implemented foundation is deliberately narrow:
7
+ What it does:
6
8
 
7
- - pre-authorized OpenID4VCI issuance of `dc+sd-jwt` credentials;
8
- - DCQL OpenID4VP requests using `direct_post.jwt` and the `x509_hash` client identifier scheme;
9
- - internal admin routes for creating offers and verification requests and reading their state;
10
- - public wallet protocol and metadata routes only;
11
- - certificate, DID-key, and Verana authorization checks before a presentation is accepted.
9
+ - pre-authorized OpenID4VCI issuance of `dc+sd-jwt` credentials, valid until they expire: the
10
+ agent hosts no status list yet, so a credential carries no `status` claim and the `ttlSeconds`
11
+ of its offer is its only bound;
12
+ - OpenID4VP requests in DCQL (`direct_post.jwt`, `x509_hash` or DID client identifier) or, for a
13
+ wallet that predates DCQL, Presentation Exchange (`direct_post`);
14
+ - the `/v2/openid4vc` Administration API scope: create an offer or a request, then list, read
15
+ and delete.
12
16
 
13
- This is implementation groundwork, not EUDI certification or a claim of complete HAIP support. W3C VCDM credentials, ISO mdoc, status lists, authorization-code issuance, and production PKI onboarding are outside this foundation. See [OpenID4VC follow-up scope](../../docs/openid4vc-w3c-follow-up.md).
17
+ Out of scope, and not implied: W3C VCDM credentials, ISO mdoc, authorization-code issuance,
18
+ wallet-attestation trust-list distribution, production PKI onboarding, formal conformance.
14
19
 
15
- ## Configure VS Agent
20
+ ## Not wired up yet
16
21
 
17
- Use the `vs-agent-openid4vc` image target. It enables `messaging,chat,openid4vc` and requires `OID4VC_CONFIG_FILE`:
22
+ The configuration file the spec defines carries no credential type and no trust setting, and the
23
+ agent derives none of them yet. So `createCredentialOffer` and `createPresentationRequest` answer
24
+ `404 UNKNOWN_ID` for every `jsonSchemaCredentialId`, `createCredentialOffer` answers it for every
25
+ `statusListId`, and reading a verified presentation answers the `RESOLVER_UNAVAILABLE` verdict.
26
+ Four issues carry the rest:
18
27
 
19
- The commands below explicitly select the Colima Docker context. Omit `--context colima` when the active Docker context already points to the intended engine.
28
+ - [#710](https://github.com/verana-labs/vs-agent/issues/710): SD-JWT VC Type Metadata, served at
29
+ the spec path `/vt/vct/{credentialSchemaId}`;
30
+ - [#711](https://github.com/verana-labs/vs-agent/issues/711): credential types read from the VPR,
31
+ one per active issuer participant;
32
+ - [#712](https://github.com/verana-labs/vs-agent/issues/712): the verifier trust decision on the
33
+ eight steps the spec now defines, with no `trust` block anywhere;
34
+ - [#713](https://github.com/verana-labs/vs-agent/issues/713): status lists.
20
35
 
21
- ```bash
22
- docker --context colima build \
23
- --target vs-agent-openid4vc \
24
- -t vs-agent-openid4vc:dev \
25
- -f apps/vs-agent/Dockerfile .
36
+ ## Enable it
26
37
 
27
- docker --context colima run --rm \
38
+ ```bash
39
+ docker run --rm \
28
40
  --env-file ./env-vars \
29
- -e OID4VC_CONFIG_FILE=/run/config/openid4vc.json \
41
+ -e OID4VC_CONFIG_FILE_LOCATION=/run/config/openid4vc.json \
30
42
  -v "$PWD/openid4vc.json:/run/config/openid4vc.json:ro" \
31
- -p 3000:3000 \
32
- -p 3001:3001 \
33
- vs-agent-openid4vc:dev
43
+ -p 3000:3000 -p 3001:3001 \
44
+ veranalabs/vs-agent
34
45
  ```
35
46
 
36
- Run both commands from the monorepo root. `env-vars` must set an HTTPS `PUBLIC_API_BASE_URL`, an `AGENT_PUBLIC_DID`, and the normal VS Agent wallet and deployment settings. The JSON file must not contain `publicApiBaseUrl`; VS Agent injects the trusted value from `PUBLIC_API_BASE_URL`. An HTTPS base path is supported and is used verbatim when composing protocol URLs. URLs containing a username or password are rejected.
47
+ `env-vars` carries the normal VS Agent settings, with an `https://` `PUBLIC_API_BASE_URL`. The
48
+ location can also be an `https://` URL, which the agent fetches once at startup without following
49
+ a redirect. With Helm, put the JSON in `openid4vc.config`.
37
50
 
38
- ### Development configuration
51
+ ## Configuration file
39
52
 
40
- This complete `openid4vc.json` shape passes the current configuration validator. The all-zero fingerprint is intentionally redacted and will trust no real peer. Replace it with the exact lowercase SHA-256 fingerprint of the self-signed issuer leaf before testing presentation:
53
+ The agent reads and validates the file at startup, and refuses to start when it cannot read the
54
+ location or validation fails. Keys are camelCase. Full reference: [[VSA-VTI-CFG-ENV-OID]](https://github.com/verana-labs/verana-spec/blob/main/v4/vs-agent/spec.md#vsa-vti-cfg-env-oid-openid4vc).
41
55
 
42
- ```json
43
- {
44
- "issuer": {
45
- "id": "development-issuer",
46
- "displayName": "Development Issuer",
47
- "signing": {
48
- "development": {
49
- "enabled": true,
50
- "commonName": "Local OpenID4VC Issuer"
51
- }
52
- }
53
- },
54
- "verifier": {
55
- "id": "development-verifier",
56
- "displayName": "Development Verifier",
57
- "signing": {
58
- "development": {
59
- "enabled": true,
60
- "commonName": "Local OpenID4VC Verifier"
61
- }
62
- }
63
- },
64
- "trust": {
65
- "resolverUrl": "https://resolver.example/v1/trust",
66
- "timeoutMs": 5000,
67
- "allowedDidWebHosts": ["issuer.example"],
68
- "credentialIssuerCertificates": [],
69
- "developmentCertificateFingerprints": [
70
- "SHA256:0000000000000000000000000000000000000000000000000000000000000000"
71
- ]
72
- },
73
- "credentialConfigurations": [
74
- {
75
- "id": "employee",
76
- "format": "dc+sd-jwt",
77
- "vct": "https://agent.example/oid4vc/vct/employee",
78
- "name": "Employee credential",
79
- "description": "Development employee credential",
80
- "vtjscId": "https://trust.example/vtjsc/employee",
81
- "claims": ["given_name", "family_name", "role"],
82
- "disclosureFrame": ["given_name", "family_name", "role"],
83
- "ttlSeconds": 3600
84
- }
85
- ],
86
- "verifierPolicies": [
87
- {
88
- "id": "employee-check",
89
- "credentialConfigurationId": "employee",
90
- "requestedClaims": ["given_name", "family_name", "role"]
91
- }
92
- ]
93
- }
94
- ```
56
+ Every key is OPTIONAL and `{}` is a valid file. The agent runs both capabilities whenever
57
+ `OID4VC_CONFIG_FILE_LOCATION` is set; the file only carries what the agent cannot derive by
58
+ itself.
59
+
60
+ | Key | Requirement |
61
+ | --- | --- |
62
+ | `issuer` | OPTIONAL. Holds `signing`, `walletAttestationCertificates` and `keyAttestationCertificates`, each OPTIONAL. |
63
+ | `issuer.walletAttestationCertificates` | X.509 roots of the accepted wallet providers. When non-empty, the agent requires a wallet attestation. |
64
+ | `issuer.keyAttestationCertificates` | Roots for OpenID4VCI key attestations. When non-empty, the issuer metadata requires a key attestation on every proof. Absent, the `attestation` proof type is neither advertised nor accepted. |
65
+ | `verifier` | OPTIONAL. Holds an OPTIONAL `signing`. |
95
66
 
96
- Development signing generates and persists a self-signed P-256 certificate for each role, with a DNS SAN derived from `PUBLIC_API_BASE_URL` and a DID URI SAN derived from `AGENT_PUBLIC_DID`. Before completing startup, the plugin publishes only those development public keys to the agent-owned DID through Credo's DID update API. Issuer keys use `assertionMethod`, verifier keys use `authentication`, and deterministic role-specific method IDs make restarts idempotent. When both roles share one DID, the plugin publishes them sequentially so neither relationship is lost.
67
+ The identifier segment of each capability is fixed: `issuer` and `verifier`, so the public paths
68
+ read `/oid4vci/issuer/...` and `/oid4vp/verifier/...`. The file declares neither, and the agent
69
+ refuses to start on an `issuer.id` or a `verifier.id`, as it does on any other unknown key.
97
70
 
98
- The certificate still requires both bindings:
71
+ The display name and the logo the agent publishes come from the ECS Service credential it holds
72
+ about its own DID; no key of the file overrides them.
99
73
 
100
- - its exact fingerprint must be configured by every verifier that accepts it;
101
- - the auto-published public key must remain present in the DID document under `assertionMethod` for issuance and `authentication` for verifier request signing.
74
+ ### Signing modes
102
75
 
103
- A pin does not replace DID key binding or Verana authorization. Development signing is unsuitable for production and is not a HAIP deployment mode.
76
+ **Development signing** (`signing` absent): the agent generates and persists a self-signed P-256
77
+ certificate for the capability, with a common name and a DNS SAN derived from
78
+ `PUBLIC_API_BASE_URL` and a DID URI SAN with the agent DID, and publishes the public key in its
79
+ DID Document before it completes startup (`assertionMethod` for the issuer, `authentication` for
80
+ the verifier). A peer verifier still has to pin the fingerprint that
81
+ `GET /v2/openid4vc/signing-certificates` returns. Unsuitable for production.
104
82
 
105
- ### Production signing and trust
83
+ **Configured signing** (`signing.configured`): `certificateChain` (a non-self-signed leaf first,
84
+ then the intermediates and the root) and the `privateJwk` P-256 key of the leaf. The leaf must
85
+ carry the agent DID as a URI SAN. The agent never publishes a configured key; the operator
86
+ publishes it under `assertionMethod` or `authentication` before startup. Keep the file out of
87
+ source control, logs and image layers.
106
88
 
107
- Configured signing material uses a non-self-signed leaf, followed by any intermediates and the root. The private JWK must be the P-256 key matching the leaf. The values below are redacted and must be supplied through a secret-managed, read-only configuration file:
89
+ ### Development example
108
90
 
109
91
  ```json
110
- {
111
- "signing": {
112
- "configured": {
113
- "certificateChain": [
114
- "MIIB...REDACTED_LEAF_BASE64...",
115
- "MIIC...REDACTED_INTERMEDIATE_BASE64...",
116
- "MIIC...REDACTED_ROOT_BASE64..."
117
- ],
118
- "privateJwk": {
119
- "kty": "EC",
120
- "crv": "P-256",
121
- "x": "REDACTED",
122
- "y": "REDACTED",
123
- "d": "REDACTED",
124
- "alg": "ES256",
125
- "kid": "issuer-signing-key"
126
- }
127
- }
128
- },
129
- "trust": {
130
- "resolverUrl": "https://resolver.example/v1/trust",
131
- "timeoutMs": 5000,
132
- "allowedDidWebHosts": ["issuer.example"],
133
- "credentialIssuerCertificates": ["MIIC...REDACTED_TRUSTED_ROOT_BASE64..."]
134
- }
135
- }
92
+ {}
136
93
  ```
137
94
 
138
- Apply the `signing` object under `issuer`, `verifier`, or both. Each leaf must contain the agent DID as a URI SAN, and its key must match the relevant DID relationship. The plugin never auto-publishes configured production keys; operators must publish them under `assertionMethod` or `authentication` themselves before startup. `credentialIssuerCertificates` contains verifier trust anchors, not peer-supplied chains. Each entry must parse as a currently valid, self-issued CA root with `keyCertSign`; duplicate roots are rejected. Do not put private JWKs in source control, logs, image layers, or public metadata.
139
-
140
- `allowedDidWebHosts` is the explicit network trust boundary for issuer DIDs read from verified certificates. Only exact `did:web` and `did:webvh` hosts on this operator-managed list are resolved. Do not populate it from a peer request or certificate. Credo owns the underlying fetch and redirect behavior, so an allowlisted host must have operator-trusted DNS and redirect behavior. Loopback, private, link-local, and unsupported DID targets are rejected before Credo resolution. Resolution returns after at most `timeoutMs`, which must not exceed 30000 milliseconds. The agent's own DID host is derived from `AGENT_PUBLIC_DID` and does not need to be repeated in this list.
95
+ A file with no key at all runs both capabilities under development signing.
141
96
 
142
- ### Configuration reference
97
+ ## Administration API
143
98
 
144
- | Field | Requirement |
145
- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
146
- | `issuer` | Optional when `verifier` is present. Defines `id`, `displayName`, and exactly one signing mode. |
147
- | `issuer.requireWalletAttestation` | Optional. When `true`, `walletAttestationCertificates` must contain locally configured X.509 roots. Trust-list distribution is not implemented. |
148
- | `verifier` | Optional when `issuer` is present. Defines `id`, `displayName`, and exactly one signing mode. |
149
- | `trust` | Required by the verifier. Defines the HTTPS Verana resolver, a 1–30000 ms resolver timeout, exact allowed issuer DID web hosts, valid issuer CA roots, and optional development leaf fingerprints in `SHA256:` plus 64 lowercase hex format. |
150
- | `credentialConfigurations` | Array of stable IDs, `dc+sd-jwt` format, VCT and VTJSC URLs, display fields, allowed claims, disclosure frame, and a 60–31,536,000 second lifetime. |
151
- | `verifierPolicies` | Array mapping a policy ID to one credential configuration and a subset of its claims. |
99
+ Every method lives under `/v2/openid4vc`, behind the Admin API authentication of the agent, and
100
+ answers in the v2 error envelope. Without `OID4VC_CONFIG_FILE_LOCATION`, every path answers `404`.
152
101
 
153
- Claims named `vct`, `iat`, `exp`, `iss`, or `cnf` are reserved for the credential envelope and cannot be configured.
102
+ | Method | Path | Notes |
103
+ | --- | --- | --- |
104
+ | `createCredentialOffer` | `POST /credential-offer` | `jsonSchemaCredentialId`, `claims`, `ttlSeconds` (60 to 7776000), optional `statusListId` and `statusListIndex` together. Returns `credentialExchangeId` and `url`. `404 UNKNOWN_ID`, `400 INVALID_INPUT`. Answers `UNKNOWN_ID` for every credential type until #711 and for every status list until #713. |
105
+ | `listCredentialExchanges` | `GET /credential-exchanges` | Filters `jsonSchemaCredentialId`, `state`. Keyset pagination. |
106
+ | `getCredentialExchange` | `GET /credential-exchanges/{credentialExchangeId}` | `credentialExchangeId`, `jsonSchemaCredentialId`, `state`, `createdAt`, `updatedAt`, `expiresAt`, `errorMessage`. Never the claims, the offer URL or the pre-authorized code. |
107
+ | `deleteCredentialExchange` | `DELETE /credential-exchanges/{credentialExchangeId}` | `204`. Deletes the record only, never a credential that a wallet holds. |
108
+ | `createPresentationRequest` | `POST /presentation-request` | `jsonSchemaCredentialId`, optional `requestedClaims` (defaults to every claim of the type), optional `queryLanguage` (`dcql`, `presentation_exchange`), optional `requestSigner` (`x5c`, `did`). Returns `proofExchangeId` and `url`. `404 UNKNOWN_ID`, `400 INVALID_INPUT`, `409 INVALID_STATE`. Answers `UNKNOWN_ID` for every credential type until #711. |
109
+ | `listPresentations` | `GET /presentations` | Filters `jsonSchemaCredentialId`, `state`. Keyset pagination. |
110
+ | `getPresentation` | `GET /presentations/{proofExchangeId}` | Adds the stored `jsonSchemaCredentialId` and `requestedClaims` of the request, then `cryptographicVerified`, `accepted`, `trust` and `credential` once the wallet answered. |
111
+ | `deletePresentation` | `DELETE /presentations/{proofExchangeId}` | `204`. |
112
+ | `listSigningCertificates` | `GET /signing-certificates` | Bare array of `role`, `development`, `fingerprint`, `certificateChain`. Never a private key. |
154
113
 
155
- ## Route ownership
114
+ Session states are credo's: `OfferCreated`, `OfferUriRetrieved`, `AuthorizationInitiated`,
115
+ `AuthorizationGranted`, `AccessTokenRequested`, `AccessTokenCreated`, `CredentialRequestReceived`,
116
+ `CredentialsPartiallyIssued`, `Completed`, `Error` for an issuance; `RequestCreated`,
117
+ `RequestUriRetrieved`, `ResponseVerified`, `Error` for a verification.
156
118
 
157
- The control and protocol surfaces are intentionally separate.
119
+ The agent stores `jsonSchemaCredentialId` and `requestedClaims` on the verification session when
120
+ it creates the request, and never infers either from the response of the wallet. A list never
121
+ decides: it reports the stored decision, and a verified session that nobody read yet shows
122
+ `cryptographicVerified: true` and `accepted: false` without `trust`.
158
123
 
159
- | Surface | Method and path | Owner and purpose |
160
- | -------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
161
- | Internal admin | `POST /v1/oid4vc/offers` | Plugin controller. Creates a pre-authorized offer from `credentialConfigurationId` and all configured claims. |
162
- | Internal admin | `GET /v1/oid4vc/offers/:id` | Plugin controller. Returns safe issuance state only. |
163
- | Internal admin | `POST /v1/oid4vc/verifier/requests` | Plugin controller. Creates a request from `policyId`. |
164
- | Internal admin | `GET /v1/oid4vc/verifier/sessions/:id` | Plugin controller. Returns protocol state and the bounded trust result. |
165
- | Internal admin | `GET /v1/oid4vc/certificates` | Plugin controller. Returns the configured roles' public signing certificates: leaf fingerprint (`SHA256:` pin format) and base64 chain. Never returns private keys. |
166
- | Public | `GET /oid4vc/vct/:configurationId` | Plugin route. Returns configured SD-JWT VC type metadata, extended with `relatedJsonSchemaCredentialId` (the ecosystem VTJSC) so wallets can verify schema governance and issuer/verifier accreditation through the Verana resolver. |
167
- | Public | dynamic paths below `/.well-known/*`, `/oid4vci/:issuerId/*`, and `/oid4vp/:verifierId/*` | Pinned Credo router. Serves issuer/OAuth metadata, returned offer and request URIs, and wallet token, credential, authorization-request, and authorization-response traffic. |
124
+ ## Public endpoints
168
125
 
169
- Credo derives protocol paths from its current route configuration and record IDs. Wallets should follow the URIs returned by the admin API and metadata rather than constructing undocumented paths.
126
+ Served on the public listener, without Admin API authentication. A wallet follows the URLs the
127
+ Admin API and the metadata return; it never builds a path itself.
170
128
 
171
- The admin routes have the default `INTERNAL` access mode. They are absent from the public listener and unavailable through the corporation bearer-authenticated listener. The internal listener is network-trusted by default, so production deployments must isolate it or place it behind an authenticated reverse proxy. Credential offers are bearer capabilities and must not be logged or exposed through a public convenience endpoint.
129
+ | Path | Purpose |
130
+ | --- | --- |
131
+ | `/.well-known/openid-credential-issuer`, `/.well-known/oauth-authorization-server`, `/.well-known/jwt-vc-issuer` | Issuer and authorization-server metadata, also at the path-inserted forms. |
132
+ | `/oid4vci/issuer/...` | Token and credential traffic of the issuer capability. |
133
+ | `/oid4vp/verifier/...` | Authorization request and response traffic of the verifier capability. |
172
134
 
173
135
  ## Trust decision
174
136
 
175
- A verifier accepts a presentation only after this sequence succeeds:
176
-
177
- 1. Credo verifies the OpenID4VP response, nonce, audience, holder binding, SD-JWT disclosure, signature, and X.509 chain against configured roots or an exact development leaf pin.
178
- 2. The plugin reads a DID only from a URI SAN after X.509 validation succeeds.
179
- 3. Before network resolution, the DID must be a well-formed `did:web` or `did:webvh` on the operator allowlist, with no loopback, private, or link-local target. The resolved document ID must exactly match the requested DID.
180
- 4. The certificate public key must match a verification method authorized by that DID document under `assertionMethod`. The plugin uses `authentication` for its own verifier request-signing certificate during startup.
181
- 5. The Verana resolver must return `TRUSTED` for the issuer DID and authorize that issuer for the credential configuration's `vtjscId`.
182
- 6. The result is accepted only for the exact `TRUSTED_AUTHORIZED` verdict.
183
-
184
- Missing or invalid chains, SAN errors, key mismatch, unresolvable DIDs, resolver timeout, malformed responses, non-`TRUSTED` status, and missing authorization all fail closed.
185
-
186
- ## Verification evidence
187
-
188
- | Path | Evidence |
189
- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
190
- | Pinned Credo `0.7.1-pr-2704-20260630143332`, in process | Automated issuance and presentation tests cover a pre-authorized offer, holder-bound `dc+sd-jwt`, DCQL, `x509_hash`, `direct_post.jwt`, `TRUSTED_AUTHORIZED` acceptance, DID-key mismatch, unauthorized issuers, resolver failure, and response replay. |
191
- | Android or EUDI wallet | No live result, wallet version, or date is recorded for this foundation branch. |
192
- | Official online interoperability tools | No result is recorded for this foundation branch. |
193
-
194
- The automated path proves behavior between local Credo agents. It is not external-wallet evidence or formal conformance evidence.
137
+ There is none yet. Nothing configures a resolver URL, allowed `did:web` hosts, credential-issuer
138
+ roots or development fingerprints: the file rejects a `trust` block, and
139
+ [#712](https://github.com/verana-labs/vs-agent/issues/712) builds the decision on the eight steps
140
+ the spec now defines, on the indexer and on DID key binding, without one. Until it lands, a
141
+ verified presentation answers `cryptographicVerified: true`, `accepted: false` and the verdict
142
+ `RESOLVER_UNAVAILABLE`, and is never accepted.
143
+
144
+ What the agent still enforces before that point: credo verifies the OpenID4VP response, the
145
+ nonce, the audience, the holder binding, the SD-JWT disclosures and the signature, and the plugin
146
+ fails a presented credential that carries no numeric `exp`. The session then ends in `Error`.
147
+
148
+ ## Wallet accommodations
149
+
150
+ The public router adapts a few responses to specific wallets, each one scoped as narrowly as the
151
+ wallet's behaviour allows: openid4vci-kt (EUDI reference wallet) accept header and
152
+ `key_attestations_required`; swiyu plain-JSON metadata and closed `ProofType` enum; wwWallet
153
+ `scope` and DPoP algorithms; NL Wallet certificate-bound signed metadata; MOSIP Inji EdDSA
154
+ request signing under the parallel did:web and Presentation Exchange details. Each lives next to
155
+ the code it changes, with a one-line note.
156
+
157
+ ## Tests
158
+
159
+ `pnpm --filter @verana-labs/vs-agent-plugin-openid4vc exec vitest run` runs the unit tests and the
160
+ in-process end-to-end tests, which start real credo agents for the issuer, the holder and the
161
+ verifier and drive a pre-authorized issuance through to a stored holder-bound credential. The
162
+ presentation round trip comes back with #712, which gives the verifier a trust anchor for the
163
+ credential it receives. No external wallet or conformance evidence is recorded here; see the
164
+ Verana Playground for recorded wallet scenarios.
package/build/config.d.ts CHANGED
@@ -1,5 +1,14 @@
1
- import type { OpenId4VcCredentialConfiguration, OpenId4VcPluginOptions, OpenId4VcVerifierPolicy } from './types';
2
- export declare function validateOpenId4VcOptions(options: OpenId4VcPluginOptions): void;
1
+ import type { OpenId4VcConfigurationFile, OpenId4VcCredentialConfiguration, OpenId4VcPluginOptions } from './types';
2
+ export declare const ISSUER_CAPABILITY_ID = "issuer";
3
+ export declare const VERIFIER_CAPABILITY_ID = "verifier";
4
+ export declare const OFFER_TTL_SECONDS_MIN = 60;
5
+ export declare const OFFER_TTL_SECONDS_MAX = 7776000;
6
+ /** [VSA-VTI-CFG-ENV-OID] Validation of the OpenID4VC configuration file. */
7
+ export declare function parseOpenId4VcConfiguration(document: unknown): OpenId4VcConfigurationFile;
3
8
  export declare function findCredentialConfiguration(options: Pick<OpenId4VcPluginOptions, 'credentialConfigurations'>, id: string): OpenId4VcCredentialConfiguration | undefined;
4
- export declare function findVerifierPolicy(options: Pick<OpenId4VcPluginOptions, 'verifierPolicies'>, id: string): OpenId4VcVerifierPolicy | undefined;
5
9
  export declare function parseOfferClaims(configuration: OpenId4VcCredentialConfiguration, input: unknown): Record<string, unknown>;
10
+ export declare function parseOfferTtlSeconds(input: unknown): number;
11
+ export declare function parseOfferIssuanceMetadata(configuration: OpenId4VcCredentialConfiguration, input: unknown): {
12
+ claims: Record<string, unknown>;
13
+ ttlSeconds: number;
14
+ };