@bedrock/vc-delivery 7.16.0 → 7.17.0
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.
- package/README.md +573 -1
- package/lib/issue.js +28 -17
- package/lib/oid4/oid4vci.js +6 -5
- package/lib/oid4/oid4vciDraft13.js +8 -6
- package/lib/storage/exchanges.js +2 -2
- package/lib/verify.js +83 -9
- package/package.json +1 -1
- package/schemas/bedrock-vc-workflow.js +54 -1
package/README.md
CHANGED
|
@@ -1 +1,573 @@
|
|
|
1
|
-
# bedrock
|
|
1
|
+
# @bedrock/vc-delivery
|
|
2
|
+
|
|
3
|
+
A [Bedrock][] module that provides a **Verifiable Credential (VC) Workflow
|
|
4
|
+
Service** for issuing and exchanging VCs using multiple protocols. It enables
|
|
5
|
+
configurable, multi-step credential exchange workflows with support for the
|
|
6
|
+
VC-API, OpenID for Verifiable Credential Issuance (OID4VCI), OpenID for
|
|
7
|
+
Verifiable Presentations (OID4VP), and an Invite Request protocol.
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Background](#background)
|
|
12
|
+
- [Features](#features)
|
|
13
|
+
- [Requirements](#requirements)
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [Quick Start](#quick-start)
|
|
16
|
+
- [Configuration](#configuration)
|
|
17
|
+
- [Workflow Config](#workflow-config)
|
|
18
|
+
- [Credential Templates](#credential-templates)
|
|
19
|
+
- [Steps](#steps)
|
|
20
|
+
- [Issuer Instances](#issuer-instances)
|
|
21
|
+
- [OID4VCI Options](#oid4vci-options)
|
|
22
|
+
- [OID4VP Client Profiles](#oid4vp-client-profiles)
|
|
23
|
+
- [HTTP API](#http-api)
|
|
24
|
+
- [Exchanges](#exchanges)
|
|
25
|
+
- [Protocols](#protocols)
|
|
26
|
+
- [OID4VCI Endpoints](#oid4vci-endpoints)
|
|
27
|
+
- [OID4VP Endpoints](#oid4vp-endpoints)
|
|
28
|
+
- [Invite Request Endpoints](#invite-request-endpoints)
|
|
29
|
+
- [Protocols](#supported-protocols)
|
|
30
|
+
- [VC-API](#vc-api)
|
|
31
|
+
- [OID4VCI](#oid4vci)
|
|
32
|
+
- [OID4VP](#oid4vp)
|
|
33
|
+
- [Invite Request](#invite-request)
|
|
34
|
+
- [Exchange Lifecycle](#exchange-lifecycle)
|
|
35
|
+
- [Multi-Step Workflows](#multi-step-workflows)
|
|
36
|
+
- [Security](#security)
|
|
37
|
+
- [Backwards Compatibility](#backwards-compatibility)
|
|
38
|
+
- [License](#license)
|
|
39
|
+
|
|
40
|
+
## Background
|
|
41
|
+
|
|
42
|
+
This module implements the server-side infrastructure for **Verifiable Credential
|
|
43
|
+
delivery**. A *workflow* is a reusable configuration that defines how credentials
|
|
44
|
+
are issued and/or verified. An *exchange* is a single instance of that workflow —
|
|
45
|
+
a live, stateful session between a workflow service and an exchange client (e.g.
|
|
46
|
+
a digital wallet).
|
|
47
|
+
|
|
48
|
+
Workflows are registered with an associated set of authorization capabilities
|
|
49
|
+
(zcaps) that grant the service permission to issue and verify credentials on
|
|
50
|
+
behalf of the workflow owner.
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- **Multi-protocol support**: VC-API, OID4VCI v1.0, OID4VP 1.0, and Invite
|
|
55
|
+
Request (with backwards compatibility for OID4VCI Draft 13 and OID4VP Draft
|
|
56
|
+
18).
|
|
57
|
+
- **Multi-step exchanges**: Define sequential steps that mix presentation
|
|
58
|
+
verification and credential issuance within a single exchange.
|
|
59
|
+
- **JSONata credential templates**: Dynamically generate credential content
|
|
60
|
+
from exchange variables using [JSONata][] expressions.
|
|
61
|
+
- **Multiple issuer instances**: Configure up to 10 independent issuer
|
|
62
|
+
instances per workflow, each with its own zcap and supported credential
|
|
63
|
+
formats.
|
|
64
|
+
- **OID4VP client profiles**: Configure up to 10 OID4VP client profiles per
|
|
65
|
+
workflow, including support for signed authorization requests and mDL/ISO
|
|
66
|
+
18013-7 presentation flows.
|
|
67
|
+
- **OAuth2 / zcap authorization**: Workflow management endpoints support both
|
|
68
|
+
zcap-based and OAuth2-based authorization.
|
|
69
|
+
- **Configurable exchange TTL**: Exchanges expire automatically (default 15
|
|
70
|
+
minutes, maximum 48 hours).
|
|
71
|
+
|
|
72
|
+
## Requirements
|
|
73
|
+
|
|
74
|
+
- Node.js >= 20
|
|
75
|
+
- MongoDB (via [`@bedrock/mongodb`][])
|
|
76
|
+
- A running [Bedrock][] application with the required peer dependencies (see
|
|
77
|
+
`peerDependencies` in `package.json`)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
## Installation
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
npm install @bedrock/vc-delivery
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Then import the module in your Bedrock application entry point:
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
import '@bedrock/vc-delivery';
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The module self-registers with Bedrock on import and will set up all routes
|
|
94
|
+
and storage on startup.
|
|
95
|
+
|
|
96
|
+
## Quick Start
|
|
97
|
+
|
|
98
|
+
### 1. Create a workflow configuration
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
// POST /workflows
|
|
102
|
+
{
|
|
103
|
+
"sequence": 0,
|
|
104
|
+
"controller": "did:key:z6Mk...",
|
|
105
|
+
"credentialTemplates": [{
|
|
106
|
+
"type": "jsonata",
|
|
107
|
+
"template": "{ '@context': ['https://www.w3.org/2018/credentials/v1'], 'type': ['VerifiableCredential'], 'credentialSubject': {'id': subject} }"
|
|
108
|
+
}],
|
|
109
|
+
"initialStep": "issue",
|
|
110
|
+
"steps": {
|
|
111
|
+
"issue": {
|
|
112
|
+
// you can use static step parameters like this, but also see
|
|
113
|
+
// `stepTemplate` below for more powerful dynamic steps instead!
|
|
114
|
+
"issueRequests": [{ "credentialTemplateIndex": 0 }]
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"zcaps": {
|
|
118
|
+
"issue": { /* delegated issue zcap */ }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 2. Create an exchange
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
// POST /workflows/:workflowId/exchanges
|
|
127
|
+
{
|
|
128
|
+
"ttl": 900,
|
|
129
|
+
"variables": {
|
|
130
|
+
"subject": "did:example:holder123"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Response: 204 No Content; Location header contains exchange URL
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 3. Execute the exchange (VC-API)
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
// POST /workflows/:workflowId/exchanges/:exchangeId
|
|
140
|
+
{}
|
|
141
|
+
// Response:
|
|
142
|
+
{
|
|
143
|
+
"verifiablePresentation": { /* VP containing issued VCs */ }
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Configuration
|
|
148
|
+
|
|
149
|
+
### Workflow Config
|
|
150
|
+
|
|
151
|
+
Workflows are created and managed via the [`@bedrock/service-core`][] HTTP
|
|
152
|
+
API at `/workflows`. The configuration body accepts the following custom
|
|
153
|
+
properties in addition to standard service-core fields:
|
|
154
|
+
|
|
155
|
+
| Property | Type | Required | Description |
|
|
156
|
+
|----------|------|----------|-------------|
|
|
157
|
+
| `credentialTemplates` | Array | No | List of JSONata credential templates to issue |
|
|
158
|
+
| `steps` | Object | No | Named step definitions for multi-step exchanges |
|
|
159
|
+
| `initialStep` | String | Required if `steps` present | Name of the first step to execute |
|
|
160
|
+
| `issuerInstances` | Array | No | Up to 10 issuer instance configurations |
|
|
161
|
+
| `zcaps` | Object | No | Authorization capability references |
|
|
162
|
+
|
|
163
|
+
If `credentialTemplates` are provided, either a top-level `zcaps.issue`
|
|
164
|
+
or at least one `issuerInstances` entry with its own issue zcap is required.
|
|
165
|
+
|
|
166
|
+
### Credential Templates
|
|
167
|
+
|
|
168
|
+
Credential templates use [JSONata][] to dynamically produce credential JSON.
|
|
169
|
+
Exchange `variables` and built-in `globals` are available as template
|
|
170
|
+
bindings.
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
"credentialTemplates": [{
|
|
174
|
+
"type": "jsonata",
|
|
175
|
+
"id": "myTemplate", // optional, for referencing in issueRequests
|
|
176
|
+
"template": "{ ... }" // JSONata expression returning a VC object
|
|
177
|
+
}]
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Built-in template globals:**
|
|
181
|
+
|
|
182
|
+
| Variable | Description |
|
|
183
|
+
|----------|-------------|
|
|
184
|
+
| `globals.exchange.id` | The local exchange ID |
|
|
185
|
+
| `globals.exchangeId` | The full exchange URL |
|
|
186
|
+
| `globals.workflow.id` | The workflow ID |
|
|
187
|
+
|
|
188
|
+
### Steps
|
|
189
|
+
|
|
190
|
+
Steps define the interactive logic of an exchange. Each step can request a
|
|
191
|
+
Verifiable Presentation, issue credentials, or both.
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
"steps": {
|
|
195
|
+
"verify": {
|
|
196
|
+
"verifiablePresentationRequest": { /* VPR object */ },
|
|
197
|
+
"nextStep": "issue"
|
|
198
|
+
},
|
|
199
|
+
"issue": {
|
|
200
|
+
"issueRequests": [
|
|
201
|
+
{ "credentialTemplateIndex": 0 }
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
"initialStep": "verify"
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Step fields:**
|
|
209
|
+
|
|
210
|
+
| Field | Description |
|
|
211
|
+
|-------|-------------|
|
|
212
|
+
| `issueRequests` | Array of credential issue request parameters |
|
|
213
|
+
| `verifiablePresentationRequest` | VPR to send to the client |
|
|
214
|
+
| `verifiablePresentation` | VP to return immediately without client input |
|
|
215
|
+
| `createChallenge` | `true` to generate and attach a challenge nonce to the VPR |
|
|
216
|
+
| `nextStep` | Name of the step to transition to after this one completes |
|
|
217
|
+
| `openId` | OID4VP options for this step |
|
|
218
|
+
| `jwtDidProofRequest` | Request a DID-bound JWT proof |
|
|
219
|
+
| `divpDidProofRequest` | Request a DI-VP DID proof |
|
|
220
|
+
| `callback.url` | URL to call when exchange state updates |
|
|
221
|
+
| `presentationSchema` | JSON Schema to validate received presentations |
|
|
222
|
+
| `verifyPresentationOptions` | Additional options for presentation verification |
|
|
223
|
+
| `verifyPresentationResultSchema` | JSON Schema to validate verification results |
|
|
224
|
+
| `allowUnprotectedPresentation` | Allow presentations without a proof |
|
|
225
|
+
| `redirectUrl` | URL to redirect the exchange client upon completion |
|
|
226
|
+
|
|
227
|
+
Steps may also be defined as **templated steps**, where a JSONata
|
|
228
|
+
`stepTemplate` expression dynamically generates the step configuration from
|
|
229
|
+
exchange variables at runtime:
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
"steps": {
|
|
233
|
+
"issue": {
|
|
234
|
+
"stepTemplate": {
|
|
235
|
+
"type": "jsonata",
|
|
236
|
+
"template": "{ 'issueRequests': [{ 'credentialTemplateIndex': 0 }] }"
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Issuer Instances
|
|
243
|
+
|
|
244
|
+
`issuerInstances` allows a workflow to configure multiple issuers, each with
|
|
245
|
+
different capabilities and supported credential media types. Up to 10 instances
|
|
246
|
+
are supported.
|
|
247
|
+
|
|
248
|
+
```js
|
|
249
|
+
"issuerInstances": [{
|
|
250
|
+
"id": "myIssuer", // optional identifier
|
|
251
|
+
"supportedMediaTypes": ["application/vc"],
|
|
252
|
+
"zcapReferenceIds": {
|
|
253
|
+
"issue": "myIssuerZcap"
|
|
254
|
+
},
|
|
255
|
+
"oid4vci": {
|
|
256
|
+
"supportedCredentialConfigurations": { // optional explicit OID4VCI config
|
|
257
|
+
"MyCredential_ldp_vc": { /* credential configuration */ }
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}]
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Each issuer instance references a zcap in the workflow's `zcaps` map by its
|
|
264
|
+
`zcapReferenceIds.issue` key.
|
|
265
|
+
|
|
266
|
+
### OID4VCI Options
|
|
267
|
+
|
|
268
|
+
When creating an exchange with OID4VCI support, pass an `openId` object in the
|
|
269
|
+
exchange creation body:
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
// POST /workflows/:workflowId/exchanges
|
|
273
|
+
{
|
|
274
|
+
"openId": {
|
|
275
|
+
"preAuthorizedCode": "abc123",
|
|
276
|
+
"oauth2": {
|
|
277
|
+
"generateKeyPair": { "algorithm": "ES256" }
|
|
278
|
+
// or provide an existing key pair:
|
|
279
|
+
// "keyPair": { "privateKeyJwk": {...}, "publicKeyJwk": {...} }
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
> **Deprecated:** `expectedCredentialRequests` is deprecated and should not be
|
|
286
|
+
> used in new workflows. Credential configurations are inferred from
|
|
287
|
+
> `issuerInstances` instead.
|
|
288
|
+
|
|
289
|
+
### OID4VP Client Profiles
|
|
290
|
+
|
|
291
|
+
OID4VP client profiles allow fine-grained control over how authorization
|
|
292
|
+
requests are constructed and which wallet/verifier protocols are used. Up to 10
|
|
293
|
+
profiles are supported per workflow step.
|
|
294
|
+
|
|
295
|
+
```js
|
|
296
|
+
// in a step:
|
|
297
|
+
"openId": {
|
|
298
|
+
"clientProfiles": {
|
|
299
|
+
"myProfile": {
|
|
300
|
+
"createAuthorizationRequest": "/authzReqVar",
|
|
301
|
+
"client_id": "https://verifier.example",
|
|
302
|
+
"client_id_scheme": "redirect_uri",
|
|
303
|
+
"response_mode": "direct_post",
|
|
304
|
+
"response_uri": "https://verifier.example/callback",
|
|
305
|
+
"protocolUrlParameters": {
|
|
306
|
+
"name": "OID4VP",
|
|
307
|
+
"scheme": "openid4vp",
|
|
308
|
+
"version": "OID4VP-1.0" // or "OID4VP-draft18"
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
> **Deprecated:** `presentation_definition` (Presentation Exchange) is a
|
|
316
|
+
> pre-OID4VP 1.0 mechanism and is deprecated. Prefer `dcql_query` for new
|
|
317
|
+
> implementations.
|
|
318
|
+
|
|
319
|
+
Supported `response_mode` values include `direct_post`, `direct_post.jwt`,
|
|
320
|
+
`dc_api`, and `dc_api.jwt`.
|
|
321
|
+
|
|
322
|
+
## HTTP API
|
|
323
|
+
|
|
324
|
+
All endpoints support CORS. Authorization uses zcaps or OAuth2 — not cookies
|
|
325
|
+
— so CSRF is not a concern.
|
|
326
|
+
|
|
327
|
+
### Exchanges
|
|
328
|
+
|
|
329
|
+
#### Create Exchange
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
POST /workflows/:workflowId/exchanges
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Body** (`application/json`):
|
|
336
|
+
|
|
337
|
+
| Field | Type | Description |
|
|
338
|
+
|-------|------|-------------|
|
|
339
|
+
| `ttl` | Number | Seconds until expiry (default: 900, max: 172800) |
|
|
340
|
+
| `expires` | String | ISO 8601 expiry date (alternative to `ttl`) |
|
|
341
|
+
| `variables` | Object | Key/value pairs available in credential templates and step templates |
|
|
342
|
+
| `openId` | Object | OID4VCI configuration (see [OID4VCI Options](#oid4vci-options)) |
|
|
343
|
+
|
|
344
|
+
> **Note:** The schema uses `additionalProperties: false`; fields not listed above (including `step`) will be rejected by the validator.
|
|
345
|
+
|
|
346
|
+
**Response**: `204 No Content` with a `Location` header pointing to the exchange URL.
|
|
347
|
+
|
|
348
|
+
#### Get Exchange
|
|
349
|
+
|
|
350
|
+
```
|
|
351
|
+
GET /workflows/:workflowId/exchanges/:exchangeId
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Returns the current exchange state. Private keys and secrets are never
|
|
355
|
+
included in the response.
|
|
356
|
+
|
|
357
|
+
#### Use Exchange (VC-API)
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
POST /workflows/:workflowId/exchanges/:exchangeId
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**Body** (`application/json`):
|
|
364
|
+
|
|
365
|
+
| Field | Type | Description |
|
|
366
|
+
|-------|------|-------------|
|
|
367
|
+
| `verifiablePresentation` | Object | VP to submit (if requested by the current step) |
|
|
368
|
+
|
|
369
|
+
**Response**: A JSON object, which may contain:
|
|
370
|
+
- `verifiablePresentation` — a VP with issued credentials
|
|
371
|
+
- `verifiablePresentationRequest` — a VPR requiring the client to submit a VP
|
|
372
|
+
|
|
373
|
+
### Protocols
|
|
374
|
+
|
|
375
|
+
#### Get Supported Protocols
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
GET /workflows/:workflowId/exchanges/:exchangeId/protocols
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Returns a `{"protocols": {...}}` object listing all interaction URLs supported
|
|
382
|
+
by the exchange. Key names are:
|
|
383
|
+
- `vcapi` — always this literal string when VC-API is supported
|
|
384
|
+
- `inviteRequest` — always this literal string when an invite request step is active
|
|
385
|
+
- OID4VCI — always `OID4VCI`
|
|
386
|
+
- OID4VP — defaults to `OID4VP` but is controlled by the `protocolUrlParameters.name`
|
|
387
|
+
field on each OID4VP client profile, so named profiles may produce arbitrary keys
|
|
388
|
+
(e.g. a profile with `"name": "bar"` produces a `bar` key)
|
|
389
|
+
|
|
390
|
+
### OID4VCI Endpoints
|
|
391
|
+
|
|
392
|
+
The well-known metadata endpoints are served at **two URL shapes** for maximum
|
|
393
|
+
wallet compatibility:
|
|
394
|
+
|
|
395
|
+
| Method | Path | Description |
|
|
396
|
+
|--------|------|-------------|
|
|
397
|
+
| `GET` | `/.well-known/openid-credential-issuer/workflows/:wfId/exchanges/:id` | Credential issuer metadata (prefix form) |
|
|
398
|
+
| `GET` | `/workflows/:wfId/exchanges/:id/.well-known/openid-credential-issuer` | Credential issuer metadata (suffix form) |
|
|
399
|
+
| `GET` | `/.well-known/oauth-authorization-server/workflows/:wfId/exchanges/:id` | AS metadata (prefix form) |
|
|
400
|
+
| `GET` | `/workflows/:wfId/exchanges/:id/.well-known/oauth-authorization-server` | AS metadata (suffix form) |
|
|
401
|
+
| `GET` | `…/exchanges/:id/openid/credential-offer` | Credential offer object |
|
|
402
|
+
| `POST` | `…/exchanges/:id/openid/token` | Access token endpoint |
|
|
403
|
+
| `POST` | `…/exchanges/:id/openid/credential` | Credential request endpoint |
|
|
404
|
+
| `POST` | `…/exchanges/:id/openid/batch_credential` | Batch credential requests |
|
|
405
|
+
| `GET` | `…/exchanges/:id/openid/jwks` | JSON Web Key Set |
|
|
406
|
+
| `POST` | `…/exchanges/:id/openid/nonce` | Nonce endpoint |
|
|
407
|
+
|
|
408
|
+
### OID4VP Endpoints
|
|
409
|
+
|
|
410
|
+
Legacy (no client profile ID) and modern (with client profile ID) routes are
|
|
411
|
+
both supported:
|
|
412
|
+
|
|
413
|
+
| Method | Path | Description |
|
|
414
|
+
|--------|------|-------------|
|
|
415
|
+
| `GET/POST` | `…/exchanges/:id/openid/client/authorization/request` | Retrieve authorization request (default profile) |
|
|
416
|
+
| `POST` | `…/exchanges/:id/openid/client/authorization/response` | Submit authorization response (default profile) |
|
|
417
|
+
| `GET/POST` | `…/exchanges/:id/openid/clients/:clientProfileId/authorization/request` | Retrieve authorization request (named profile) |
|
|
418
|
+
| `POST` | `…/exchanges/:id/openid/clients/:clientProfileId/authorization/response` | Submit authorization response (named profile) |
|
|
419
|
+
|
|
420
|
+
### Invite Request Endpoints
|
|
421
|
+
|
|
422
|
+
| Method | Path | Description |
|
|
423
|
+
|--------|------|-------------|
|
|
424
|
+
| `POST` | `…/exchanges/:id/invite-request/response` | Submit invite response |
|
|
425
|
+
|
|
426
|
+
## Supported Protocols
|
|
427
|
+
|
|
428
|
+
### VC-API
|
|
429
|
+
|
|
430
|
+
The [VC-API][] protocol is automatically supported for any workflow that has
|
|
431
|
+
`credentialTemplates`, or for any step that has a
|
|
432
|
+
`verifiablePresentationRequest` or `verifiablePresentation`. The exchange URL
|
|
433
|
+
itself serves as the `vcapi` interaction endpoint.
|
|
434
|
+
|
|
435
|
+
### OID4VCI
|
|
436
|
+
|
|
437
|
+
[OpenID for Verifiable Credential Issuance][OID4VCI-spec] is supported when
|
|
438
|
+
an exchange is created with an `openId` configuration object. Supported grant
|
|
439
|
+
types:
|
|
440
|
+
|
|
441
|
+
- Pre-authorized code flow (`urn:ietf:params:oauth:grant-type:pre-authorized_code`)
|
|
442
|
+
|
|
443
|
+
Supported proof types for credential requests:
|
|
444
|
+
|
|
445
|
+
- `jwt` (JWT-based DID proof)
|
|
446
|
+
- `di_vp` (Data Integrity VP DID proof)
|
|
447
|
+
|
|
448
|
+
Supported credential formats (OID4VCI v1.0):
|
|
449
|
+
|
|
450
|
+
- `ldp_vc` / `application/vc` (JSON-LD Verifiable Credential)
|
|
451
|
+
- `jwt_vc_json` / `application/jwt` (JWT Verifiable Credential)
|
|
452
|
+
|
|
453
|
+
Supported for backwards compatibility with OID4VCI Draft 13 (deprecated):
|
|
454
|
+
|
|
455
|
+
- `jwt_vc_json-ld` (JSON-LD-based VC-JWT envelope)
|
|
456
|
+
|
|
457
|
+
> **Deprecated:** OID4VCI Draft 13 credential request formats are supported
|
|
458
|
+
> for backwards compatibility but are deprecated. New implementations should
|
|
459
|
+
> use OID4VCI v1.0.
|
|
460
|
+
|
|
461
|
+
### OID4VP
|
|
462
|
+
|
|
463
|
+
[OpenID for Verifiable Presentations][OID4VP-spec] is supported when a step
|
|
464
|
+
includes an `openId` configuration. The module supports:
|
|
465
|
+
|
|
466
|
+
- OID4VP 1.0+ (with prefixed `client_id` schemes and `request_uri_method=post`)
|
|
467
|
+
- Signed authorization requests (via `authorizationRequestSigningParameters`)
|
|
468
|
+
- Multiple response modes: `direct_post`, `direct_post.jwt`, `dc_api`, `dc_api.jwt`
|
|
469
|
+
- mDL / ISO 18013-7 presentations (Annex B, C, and D handover types)
|
|
470
|
+
- DCQL queries (`dcql_query`)
|
|
471
|
+
|
|
472
|
+
Supported for backwards compatibility (deprecated):
|
|
473
|
+
|
|
474
|
+
- OID4VP Draft 18
|
|
475
|
+
- Presentation Definitions (`presentation_definition`)
|
|
476
|
+
|
|
477
|
+
### Invite Request
|
|
478
|
+
|
|
479
|
+
A lightweight invite-based protocol. When a step exposes an `inviteRequest`
|
|
480
|
+
property, the exchange will include an `inviteRequest` URL in its `protocols`
|
|
481
|
+
response. The client POSTs an invite response (containing a `url`, `purpose`,
|
|
482
|
+
and optional `referenceId`) to complete this step.
|
|
483
|
+
|
|
484
|
+
Because the static `computedStep` schema does not declare an `inviteRequest`
|
|
485
|
+
property (it uses `additionalProperties: false`), the property must be injected
|
|
486
|
+
at runtime via a `stepTemplate` rather than declared directly in the workflow
|
|
487
|
+
configuration:
|
|
488
|
+
|
|
489
|
+
```js
|
|
490
|
+
"steps": {
|
|
491
|
+
"myStep": {
|
|
492
|
+
"stepTemplate": {
|
|
493
|
+
"type": "jsonata",
|
|
494
|
+
"template": "{ \"inviteRequest\": inviteRequest }"
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
The exchange variable `inviteRequest` must be set to `true` when creating the
|
|
501
|
+
exchange to activate the invite request protocol for that step.
|
|
502
|
+
|
|
503
|
+
## Exchange Lifecycle
|
|
504
|
+
|
|
505
|
+
```mermaid
|
|
506
|
+
graph TD
|
|
507
|
+
A[Create Exchange]:::accent0 --> B[Exchange Pending]:::accent1
|
|
508
|
+
B --> C{Protocol}:::accent2
|
|
509
|
+
C -->|VC-API| D[POST to exchange URL]
|
|
510
|
+
C -->|OID4VCI| E[Pre-auth code flow]
|
|
511
|
+
C -->|OID4VP| F[Fetch authz request]
|
|
512
|
+
C -->|Invite| G[POST invite response]
|
|
513
|
+
D & E & F & G --> H{Step complete?}:::accent3
|
|
514
|
+
H -->|Next step exists| B
|
|
515
|
+
H -->|No more steps| I[Exchange Complete]:::accent4
|
|
516
|
+
B -->|TTL expires| J[Exchange Expired]:::accent5
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Exchanges are stored in MongoDB. By default they expire after **15 minutes**
|
|
520
|
+
(`ttl: 900`). The maximum TTL is **48 hours** (`ttl: 172800`). Expired
|
|
521
|
+
exchanges are retained for an additional grace period of 3 days before being
|
|
522
|
+
eligible for removal.
|
|
523
|
+
|
|
524
|
+
Exchange `variables` hold the mutable state that flows between steps, including
|
|
525
|
+
per-step results accessible at `variables.results[stepName]`.
|
|
526
|
+
|
|
527
|
+
## Multi-Step Workflows
|
|
528
|
+
|
|
529
|
+
A workflow can chain multiple steps using the `nextStep` field. This enables
|
|
530
|
+
patterns such as:
|
|
531
|
+
|
|
532
|
+
1. **Verify then Issue** — Require the holder to present an existing credential
|
|
533
|
+
before receiving a new one.
|
|
534
|
+
2. **OID4VP then OID4VCI** — Verify via OID4VP and then issue via OID4VCI in
|
|
535
|
+
the same exchange session.
|
|
536
|
+
3. **Invite then Issue** — Collect wallet/app metadata via invite request
|
|
537
|
+
before issuing credentials.
|
|
538
|
+
|
|
539
|
+
Step results are stored in `exchange.variables.results[stepName]` and are
|
|
540
|
+
accessible in subsequent JSONata step templates or credential templates.
|
|
541
|
+
|
|
542
|
+
## Security
|
|
543
|
+
|
|
544
|
+
- Workflow management routes require **zcap** or **OAuth2** authorization.
|
|
545
|
+
- Exchange execution routes (VC-API `POST`) are unauthenticated by design,
|
|
546
|
+
relying on capability URLs for authorization; exchanges are single-use,
|
|
547
|
+
short-lived, and scoped to a specific workflow. Workflows themselves
|
|
548
|
+
can add authentication by asking for verifiable presentations and
|
|
549
|
+
verifiable credentials.
|
|
550
|
+
- CORS is enabled on all endpoints. This is safe because authorization is
|
|
551
|
+
performed using HTTP signatures and capabilities (not cookies), making CSRF
|
|
552
|
+
impossible.
|
|
553
|
+
- Private key material (`privateKeyJwk`) and exchange `secrets` are stripped
|
|
554
|
+
from all API responses.
|
|
555
|
+
- VC-API exchange creation and exchange-use payloads are limited to **10 MB** (configured via `config.express.bodyParser.routes`). OID4VP authorization-response form posts are also limited to **10 MB** (via an inline `urlencoded` body parser). Other OID4VCI endpoints (token, credential, etc.) rely on the framework's default body-size limits. A feature exists to enable larger exchange state, but it has not been enabled at this time.
|
|
556
|
+
|
|
557
|
+
## Backwards Compatibility
|
|
558
|
+
|
|
559
|
+
This module previously exposed a `vc-exchanger` service at `/exchangers`. That
|
|
560
|
+
service type and route prefix are still registered as aliases and will continue
|
|
561
|
+
to function, but new deployments should use `vc-workflow` and `/workflows`.
|
|
562
|
+
|
|
563
|
+
## License
|
|
564
|
+
|
|
565
|
+
[New BSD License](LICENSE.md) © 2022-2026 Digital Bazaar, Inc.
|
|
566
|
+
|
|
567
|
+
[Bedrock]: https://github.com/digitalbazaar/bedrock
|
|
568
|
+
[JSONata]: https://jsonata.org
|
|
569
|
+
[VC-API]: https://w3c-ccg.github.io/vc-api/
|
|
570
|
+
[OID4VCI-spec]: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html
|
|
571
|
+
[OID4VP-spec]: https://openid.net/specs/openid-4-verifiable-presentations-1_0.html
|
|
572
|
+
[`@bedrock/mongodb`]: https://github.com/digitalbazaar/bedrock-mongodb
|
|
573
|
+
[`@bedrock/service-core`]: https://github.com/digitalbazaar/bedrock-service-core
|
package/lib/issue.js
CHANGED
|
@@ -162,25 +162,36 @@ async function _evalIssueRequests({
|
|
|
162
162
|
}));
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
function _getIssueZcap({
|
|
165
|
+
function _getIssueZcap({
|
|
166
|
+
issuerInstances, zcaps, issueRequestParams, mediaType
|
|
167
|
+
}) {
|
|
166
168
|
const {
|
|
167
169
|
issuerInstanceId,
|
|
168
170
|
oid4vci: {credentialConfigurationId} = {}
|
|
169
|
-
} =
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
171
|
+
} = issueRequestParams;
|
|
172
|
+
const instance = issuerInstances.find(instance => {
|
|
173
|
+
const {id, oid4vci, supportedMediaTypes} = instance;
|
|
174
|
+
// find by `issuerInstanceId` if given
|
|
175
|
+
if(id && id === issuerInstanceId) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
// find by `credentialConfigurationId` if given
|
|
179
|
+
if(credentialConfigurationId && oid4vci) {
|
|
180
|
+
return oid4vci.supportedCredentialConfigurations
|
|
181
|
+
[credentialConfigurationId] !== undefined;
|
|
182
|
+
}
|
|
183
|
+
// find by `mediaType` instead
|
|
184
|
+
return supportedMediaTypes.includes(mediaType);
|
|
185
|
+
});
|
|
186
|
+
if(!instance) {
|
|
187
|
+
throw new BedrockError(
|
|
188
|
+
'Workflow step "issueRequest" configuration error; ' +
|
|
189
|
+
'no matching issuer instance found.', {
|
|
190
|
+
name: 'DataError',
|
|
191
|
+
details: {httpStatusCode: 500, public: true}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
const {zcapReferenceIds: {issue: issueRefId}} = instance;
|
|
184
195
|
return zcaps[issueRefId];
|
|
185
196
|
}
|
|
186
197
|
|
|
@@ -199,7 +210,7 @@ async function _issue({workflow, exchange, issueRequests, mediaType} = {}) {
|
|
|
199
210
|
|
|
200
211
|
// get the zcap to use for the issue request
|
|
201
212
|
const capability = _getIssueZcap({
|
|
202
|
-
issuerInstances, zcaps,
|
|
213
|
+
issuerInstances, zcaps, issueRequestParams: params, mediaType
|
|
203
214
|
});
|
|
204
215
|
|
|
205
216
|
// specify URL to `/credentials/issue` to handle case that capability
|
package/lib/oid4/oid4vci.js
CHANGED
|
@@ -451,7 +451,7 @@ export async function processCredentialRequests({req, res, isBatchRequest}) {
|
|
|
451
451
|
}
|
|
452
452
|
|
|
453
453
|
// verify every DID proof and get resulting DIDs
|
|
454
|
-
const
|
|
454
|
+
const credentialRequestProofResults = await Promise.all(
|
|
455
455
|
credentialRequests.map(async credentialRequest => {
|
|
456
456
|
return verifyCredentialRequestProof({
|
|
457
457
|
credentialRequest, workflow, exchange
|
|
@@ -459,17 +459,18 @@ export async function processCredentialRequests({req, res, isBatchRequest}) {
|
|
|
459
459
|
}));
|
|
460
460
|
// require `did` to be the same for every proof
|
|
461
461
|
// FIXME: determine if this needs to be more flexible
|
|
462
|
-
const did =
|
|
463
|
-
if(
|
|
462
|
+
const did = credentialRequestProofResults[0].did;
|
|
463
|
+
if(credentialRequestProofResults.some(({did: d}) => did !== d)) {
|
|
464
464
|
// FIXME: improve error
|
|
465
465
|
throw new Error('every DID must be the same');
|
|
466
466
|
}
|
|
467
|
-
// store
|
|
467
|
+
// store results in variables associated with current step
|
|
468
468
|
exchange.variables.results[exchange.step] = {
|
|
469
469
|
...exchange.variables.results[exchange.step],
|
|
470
470
|
// common use case of DID Authentication; provide `did` for ease
|
|
471
471
|
// of use in templates
|
|
472
|
-
did
|
|
472
|
+
did,
|
|
473
|
+
credentialRequestProofResults
|
|
473
474
|
};
|
|
474
475
|
}
|
|
475
476
|
},
|
|
@@ -25,12 +25,14 @@ export function createSupportedCredentialConfigurations({
|
|
|
25
25
|
|
|
26
26
|
// for every expected credential request and supported format, generate a
|
|
27
27
|
// supported credential configuration
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
if(expectedCredentialRequests) {
|
|
29
|
+
for(const credentialRequest of expectedCredentialRequests) {
|
|
30
|
+
const configurations = _createCredentialConfigurations({
|
|
31
|
+
credentialRequest, supportedFormats, step
|
|
32
|
+
});
|
|
33
|
+
for(const {id, configuration} of configurations) {
|
|
34
|
+
supported.set(id, configuration);
|
|
35
|
+
}
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
|
package/lib/storage/exchanges.js
CHANGED
|
@@ -183,7 +183,7 @@ export async function get({
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
let record = await collection.findOne(query, {projection});
|
|
186
|
-
if(!allowExpired) {
|
|
186
|
+
if(record && !allowExpired) {
|
|
187
187
|
// ensure `expires` is enforced programmatically even if background job
|
|
188
188
|
// has not yet removed the record; force unexpiring exchanges to be not
|
|
189
189
|
// found via this code path -- any exchanges without an expiration date
|
|
@@ -191,7 +191,7 @@ export async function get({
|
|
|
191
191
|
// in the database
|
|
192
192
|
const now = new Date();
|
|
193
193
|
// note: for undefined `expires`, this will be `NaN || now` => `now`
|
|
194
|
-
const expires = new Date(record.exchange.expires) || now;
|
|
194
|
+
const expires = new Date(Date.parse(record.exchange.expires) || now);
|
|
195
195
|
if(now >= expires) {
|
|
196
196
|
record = null;
|
|
197
197
|
}
|
package/lib/verify.js
CHANGED
|
@@ -157,16 +157,35 @@ export async function verifyCredentialRequestProof({
|
|
|
157
157
|
|
|
158
158
|
let _did;
|
|
159
159
|
const dids = [];
|
|
160
|
+
const verifyResults = {};
|
|
160
161
|
if(diVp) {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
const result = await verifyDidProofDiVp({workflow, exchange, diVp});
|
|
163
|
+
verifyResults.di_vp = verifyResults.di_vp ?? [];
|
|
164
|
+
// return results w/both common key formats
|
|
165
|
+
verifyResults.di_vp.push({
|
|
166
|
+
did: result.did,
|
|
167
|
+
verificationMethod: await _toMultikey({
|
|
168
|
+
vm: result.verifyResult.verificationMethod
|
|
169
|
+
}),
|
|
170
|
+
publicKeyJwk: await _toJwk(result.verifyResult.verificationMethod)
|
|
171
|
+
});
|
|
172
|
+
dids.push(result.did);
|
|
173
|
+
_did = result.did;
|
|
164
174
|
}
|
|
165
175
|
if(jwt) {
|
|
166
|
-
const
|
|
167
|
-
|
|
176
|
+
const result = await verifyDidProofJwt({workflow, exchange, jwt});
|
|
177
|
+
verifyResults.jwt = verifyResults.jwt ?? [];
|
|
178
|
+
// return results w/both common key formats
|
|
179
|
+
verifyResults.jwt.push({
|
|
180
|
+
did: result.did,
|
|
181
|
+
verificationMethod: await _toMultikey({
|
|
182
|
+
jwk: result.verifyResult.jwk, controller: result.did
|
|
183
|
+
}),
|
|
184
|
+
publicKeyJwk: result.verifyResult.jwk
|
|
185
|
+
});
|
|
186
|
+
dids.push(result.did);
|
|
168
187
|
if(_did === undefined) {
|
|
169
|
-
_did = did;
|
|
188
|
+
_did = result.did;
|
|
170
189
|
}
|
|
171
190
|
}
|
|
172
191
|
|
|
@@ -175,7 +194,7 @@ export async function verifyCredentialRequestProof({
|
|
|
175
194
|
throw new Error('every DID must be the same');
|
|
176
195
|
}
|
|
177
196
|
|
|
178
|
-
return _did;
|
|
197
|
+
return {did: _did, credentialRequest, verifyResults};
|
|
179
198
|
}
|
|
180
199
|
|
|
181
200
|
export async function verifyDidProofDiVp({workflow, exchange, diVp} = {}) {
|
|
@@ -191,6 +210,7 @@ export async function verifyDidProofDiVp({workflow, exchange, diVp} = {}) {
|
|
|
191
210
|
expectedChallenge: exchange.id,
|
|
192
211
|
expectedDomain: exchangeId
|
|
193
212
|
});
|
|
213
|
+
// FIXME: ensure cryptosuite used matches a supported `di_vp` proof type
|
|
194
214
|
const did = verifyResult.verificationMethod.controller;
|
|
195
215
|
return {verified: true, did, verifyResult};
|
|
196
216
|
}
|
|
@@ -208,6 +228,7 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
|
|
|
208
228
|
|
|
209
229
|
let issuer;
|
|
210
230
|
// `resolveKey` is passed `protectedHeader`
|
|
231
|
+
let jwk;
|
|
211
232
|
const resolveKey = async ({alg, kid}) => {
|
|
212
233
|
const isEcdsa = ECDSA_ALGS.includes(alg);
|
|
213
234
|
const isEddsa = !isEcdsa && EDDSA_ALGS.includes(alg);
|
|
@@ -258,7 +279,6 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
|
|
|
258
279
|
}
|
|
259
280
|
});
|
|
260
281
|
}
|
|
261
|
-
let jwk;
|
|
262
282
|
if(isEcdsa) {
|
|
263
283
|
const keyPair = await EcdsaMultikey.from(vm);
|
|
264
284
|
jwk = await EcdsaMultikey.toJwk({keyPair});
|
|
@@ -272,6 +292,7 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
|
|
|
272
292
|
};
|
|
273
293
|
|
|
274
294
|
// FIXME: indicate supported signatures
|
|
295
|
+
// FIXME: ensure algorithm used matches a supported `jwt` algs
|
|
275
296
|
// const allowedAlgorithms = [];
|
|
276
297
|
|
|
277
298
|
// use `jose` lib (for now) to verify JWT and return `payload`;
|
|
@@ -285,7 +306,7 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
|
|
|
285
306
|
audience,
|
|
286
307
|
clockTolerance: maxClockSkew
|
|
287
308
|
});
|
|
288
|
-
verifyResult = {payload, protectedHeader};
|
|
309
|
+
verifyResult = {payload, protectedHeader, jwk};
|
|
289
310
|
} catch(e) {
|
|
290
311
|
const details = {
|
|
291
312
|
httpStatusCode: 403,
|
|
@@ -332,3 +353,56 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
|
|
|
332
353
|
|
|
333
354
|
return {verified: true, did: issuer, verifyResult};
|
|
334
355
|
}
|
|
356
|
+
|
|
357
|
+
function _getJwkAlgFromMultibase(mb) {
|
|
358
|
+
if(mb.startsWith('zDna')) {
|
|
359
|
+
return 'ES256';
|
|
360
|
+
}
|
|
361
|
+
if(mb.startsWith('z82L')) {
|
|
362
|
+
return 'ES384';
|
|
363
|
+
}
|
|
364
|
+
if(mb.startsWith('z6Mk')) {
|
|
365
|
+
return 'EdDSA';
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function _toJwk(vm) {
|
|
370
|
+
let jwk;
|
|
371
|
+
const alg = _getJwkAlgFromMultibase(vm.publicKeyMultibase);
|
|
372
|
+
if(alg === 'EdDSA') {
|
|
373
|
+
const keyPair = await Ed25519Multikey.from(vm);
|
|
374
|
+
jwk = await Ed25519Multikey.toJwk({keyPair});
|
|
375
|
+
jwk.alg = 'EdDSA';
|
|
376
|
+
} else if(alg?.startsWith('ES')) {
|
|
377
|
+
const keyPair = await EcdsaMultikey.from(vm);
|
|
378
|
+
jwk = await EcdsaMultikey.toJwk({keyPair});
|
|
379
|
+
jwk.alg = alg;
|
|
380
|
+
}
|
|
381
|
+
return jwk;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function _toMultikey({vm, jwk, controller}) {
|
|
385
|
+
let keyPair;
|
|
386
|
+
if(jwk) {
|
|
387
|
+
if(jwk.alg === 'EdDSA') {
|
|
388
|
+
keyPair = await Ed25519Multikey.fromJwk({jwk, controller});
|
|
389
|
+
}
|
|
390
|
+
if(jwk.alg.startsWith('ES')) {
|
|
391
|
+
keyPair = await EcdsaMultikey.fromJwk({jwk, controller});
|
|
392
|
+
}
|
|
393
|
+
} else if(vm) {
|
|
394
|
+
if(vm.type === 'Multikey') {
|
|
395
|
+
return vm;
|
|
396
|
+
}
|
|
397
|
+
const alg = _getJwkAlgFromMultibase(vm.publicKeyMultibase);
|
|
398
|
+
if(alg === 'EdDSA') {
|
|
399
|
+
keyPair = await Ed25519Multikey.from(vm);
|
|
400
|
+
} else if(alg.startsWith('ES')) {
|
|
401
|
+
keyPair = await EcdsaMultikey.from(vm);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if(keyPair) {
|
|
406
|
+
return keyPair.export();
|
|
407
|
+
}
|
|
408
|
+
}
|
package/package.json
CHANGED
|
@@ -232,6 +232,57 @@ function credentialConfiguration() {
|
|
|
232
232
|
};
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
function mdocCredentialConfiguration() {
|
|
236
|
+
return {
|
|
237
|
+
title: 'OID4VCI Credential Configuration for mdocs',
|
|
238
|
+
type: 'object',
|
|
239
|
+
required: [
|
|
240
|
+
'cryptographic_binding_methods_supported',
|
|
241
|
+
'credential_signing_alg_values_supported',
|
|
242
|
+
'doctype',
|
|
243
|
+
'format',
|
|
244
|
+
'proof_types_supported'
|
|
245
|
+
],
|
|
246
|
+
additionalProperties: false,
|
|
247
|
+
properties: {
|
|
248
|
+
cryptographic_binding_methods_supported: {
|
|
249
|
+
type: 'array',
|
|
250
|
+
minItems: 1,
|
|
251
|
+
maxItems: 1,
|
|
252
|
+
items: {
|
|
253
|
+
type: 'string',
|
|
254
|
+
enum: ['cose_key']
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
credential_signing_alg_values_supported: {
|
|
258
|
+
// schema could be improved to ensure array is const: [-7, -9]; but
|
|
259
|
+
// other values might be acceptable in the future
|
|
260
|
+
type: 'array',
|
|
261
|
+
minItems: 2,
|
|
262
|
+
maxItems: 2,
|
|
263
|
+
items: {
|
|
264
|
+
type: 'number',
|
|
265
|
+
enum: [-7, -9]
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
doctype: {
|
|
269
|
+
const: 'org.iso.18013.5.1.mDL'
|
|
270
|
+
},
|
|
271
|
+
format: {
|
|
272
|
+
type: 'string',
|
|
273
|
+
enum: ['mso_mdoc']
|
|
274
|
+
},
|
|
275
|
+
proof_types_supported: {
|
|
276
|
+
type: 'object',
|
|
277
|
+
properties: {
|
|
278
|
+
di_vp: supportedProofTypeConfiguration,
|
|
279
|
+
jwt: supportedProofTypeConfiguration
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
235
286
|
const openIdExchangeOptions = {
|
|
236
287
|
title: 'OpenID Exchange options',
|
|
237
288
|
type: 'object',
|
|
@@ -367,7 +418,9 @@ const issuerInstance = {
|
|
|
367
418
|
type: 'object',
|
|
368
419
|
additionalProperties: false,
|
|
369
420
|
patternProperties: {
|
|
370
|
-
'^.*$':
|
|
421
|
+
'^.*$': {
|
|
422
|
+
oneOf: [credentialConfiguration(), mdocCredentialConfiguration()]
|
|
423
|
+
}
|
|
371
424
|
}
|
|
372
425
|
}
|
|
373
426
|
}
|