@sphereon/ssi-sdk.vc-status-list 0.32.1-next.13 → 0.32.1-next.145
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/dist/functions.d.ts +13 -13
- package/dist/functions.d.ts.map +1 -1
- package/dist/functions.js +53 -109
- package/dist/functions.js.map +1 -1
- package/dist/impl/IStatusList.d.ts +26 -0
- package/dist/impl/IStatusList.d.ts.map +1 -0
- package/dist/impl/IStatusList.js +3 -0
- package/dist/impl/IStatusList.js.map +1 -0
- package/dist/impl/OAuthStatusList.d.ts +20 -0
- package/dist/impl/OAuthStatusList.d.ts.map +1 -0
- package/dist/impl/OAuthStatusList.js +147 -0
- package/dist/impl/OAuthStatusList.js.map +1 -0
- package/dist/impl/StatusList2021.d.ts +15 -0
- package/dist/impl/StatusList2021.d.ts.map +1 -0
- package/dist/impl/StatusList2021.js +170 -0
- package/dist/impl/StatusList2021.js.map +1 -0
- package/dist/impl/StatusListFactory.d.ts +11 -0
- package/dist/impl/StatusListFactory.d.ts.map +1 -0
- package/dist/impl/StatusListFactory.js +32 -0
- package/dist/impl/StatusListFactory.js.map +1 -0
- package/dist/impl/encoding/cbor.d.ts +6 -0
- package/dist/impl/encoding/cbor.d.ts.map +1 -0
- package/dist/impl/encoding/cbor.js +140 -0
- package/dist/impl/encoding/cbor.js.map +1 -0
- package/dist/impl/encoding/common.d.ts +12 -0
- package/dist/impl/encoding/common.d.ts.map +1 -0
- package/dist/impl/encoding/common.js +26 -0
- package/dist/impl/encoding/common.js.map +1 -0
- package/dist/impl/encoding/jwt.d.ts +9 -0
- package/dist/impl/encoding/jwt.d.ts.map +1 -0
- package/dist/impl/encoding/jwt.js +74 -0
- package/dist/impl/encoding/jwt.js.map +1 -0
- package/dist/types/index.d.ts +115 -30
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +12 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils.d.ts +17 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +88 -0
- package/dist/utils.js.map +1 -0
- package/package.json +11 -3
- package/src/functions.ts +73 -159
- package/src/impl/IStatusList.ts +42 -0
- package/src/impl/OAuthStatusList.ts +196 -0
- package/src/impl/StatusList2021.ts +223 -0
- package/src/impl/StatusListFactory.ts +34 -0
- package/src/impl/encoding/cbor.ts +171 -0
- package/src/impl/encoding/common.ts +25 -0
- package/src/impl/encoding/jwt.ts +80 -0
- package/src/types/index.ts +132 -34
- package/src/utils.ts +95 -0
package/src/functions.ts
CHANGED
|
@@ -2,40 +2,42 @@ import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resoluti
|
|
|
2
2
|
import {
|
|
3
3
|
CredentialMapper,
|
|
4
4
|
DocumentFormat,
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
ProofFormat,
|
|
6
|
+
StatusListCredential,
|
|
7
7
|
StatusListDriverType,
|
|
8
8
|
StatusListType,
|
|
9
9
|
StatusPurpose2021,
|
|
10
10
|
} from '@sphereon/ssi-types'
|
|
11
|
+
import { CredentialStatus, DIDDocument, IAgentContext, ICredentialPlugin, ProofFormat as VeramoProofFormat } from '@veramo/core'
|
|
11
12
|
|
|
12
|
-
import { checkStatus
|
|
13
|
-
import { CredentialStatus, DIDDocument, IAgentContext, ICredentialPlugin, ProofFormat } from '@veramo/core'
|
|
13
|
+
import { checkStatus } from '@sphereon/vc-status-list'
|
|
14
14
|
import { CredentialJwtOrJSON, StatusMethod } from 'credential-status'
|
|
15
15
|
import {
|
|
16
16
|
CreateNewStatusListFuncArgs,
|
|
17
|
+
Status2021,
|
|
17
18
|
StatusList2021ToVerifiableCredentialArgs,
|
|
18
|
-
StatusListDetails,
|
|
19
19
|
StatusListResult,
|
|
20
|
+
StatusOAuth,
|
|
20
21
|
UpdateStatusListFromEncodedListArgs,
|
|
21
|
-
|
|
22
|
+
UpdateStatusListIndexArgs,
|
|
22
23
|
} from './types'
|
|
24
|
+
import { assertValidProofType, determineStatusListType, getAssertedValue, getAssertedValues } from './utils'
|
|
25
|
+
import { getStatusListImplementation } from './impl/StatusListFactory'
|
|
23
26
|
|
|
24
|
-
export async function fetchStatusListCredential(args: { statusListCredential: string }): Promise<
|
|
27
|
+
export async function fetchStatusListCredential(args: { statusListCredential: string }): Promise<StatusListCredential> {
|
|
25
28
|
const url = getAssertedValue('statusListCredential', args.statusListCredential)
|
|
26
29
|
try {
|
|
27
30
|
const response = await fetch(url)
|
|
28
31
|
if (!response.ok) {
|
|
29
|
-
|
|
30
|
-
throw Error(error)
|
|
32
|
+
throw Error(`Fetching status list ${url} resulted in an error: ${response.status} : ${response.statusText}`)
|
|
31
33
|
}
|
|
32
34
|
const responseAsText = await response.text()
|
|
33
35
|
if (responseAsText.trim().startsWith('{')) {
|
|
34
|
-
return JSON.parse(responseAsText) as
|
|
36
|
+
return JSON.parse(responseAsText) as StatusListCredential
|
|
35
37
|
}
|
|
36
|
-
return responseAsText as
|
|
38
|
+
return responseAsText as StatusListCredential
|
|
37
39
|
} catch (error) {
|
|
38
|
-
console.
|
|
40
|
+
console.error(`Fetching status list ${url} resulted in an unexpected error: ${error instanceof Error ? error.message : JSON.stringify(error)}`)
|
|
39
41
|
throw error
|
|
40
42
|
}
|
|
41
43
|
}
|
|
@@ -52,7 +54,7 @@ export function statusPluginStatusFunction(args: {
|
|
|
52
54
|
const result = await checkStatusForCredential({
|
|
53
55
|
...args,
|
|
54
56
|
documentLoader: args.documentLoader,
|
|
55
|
-
credential: credential as
|
|
57
|
+
credential: credential as StatusListCredential,
|
|
56
58
|
errorUnknownListType: args.errorUnknownListType,
|
|
57
59
|
})
|
|
58
60
|
|
|
@@ -75,7 +77,7 @@ export function vcLibCheckStatusFunction(args: {
|
|
|
75
77
|
}) {
|
|
76
78
|
const { mandatoryCredentialStatus, verifyStatusListCredential, verifyMatchingIssuers, errorUnknownListType } = args
|
|
77
79
|
return (args: {
|
|
78
|
-
credential:
|
|
80
|
+
credential: StatusListCredential
|
|
79
81
|
documentLoader: any
|
|
80
82
|
suite: any
|
|
81
83
|
}): Promise<{
|
|
@@ -93,7 +95,7 @@ export function vcLibCheckStatusFunction(args: {
|
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
export async function checkStatusForCredential(args: {
|
|
96
|
-
credential:
|
|
98
|
+
credential: StatusListCredential
|
|
97
99
|
documentLoader: any
|
|
98
100
|
suite: any
|
|
99
101
|
mandatoryCredentialStatus?: boolean
|
|
@@ -132,7 +134,7 @@ export async function simpleCheckStatusFromStatusListUrl(args: {
|
|
|
132
134
|
type?: StatusListType | 'StatusList2021Entry'
|
|
133
135
|
id?: string
|
|
134
136
|
statusListIndex: string
|
|
135
|
-
}): Promise<
|
|
137
|
+
}): Promise<number | Status2021 | StatusOAuth> {
|
|
136
138
|
return checkStatusIndexFromStatusListCredential({
|
|
137
139
|
...args,
|
|
138
140
|
statusListCredential: await fetchStatusListCredential(args),
|
|
@@ -140,163 +142,97 @@ export async function simpleCheckStatusFromStatusListUrl(args: {
|
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
export async function checkStatusIndexFromStatusListCredential(args: {
|
|
143
|
-
statusListCredential:
|
|
145
|
+
statusListCredential: StatusListCredential
|
|
144
146
|
statusPurpose?: StatusPurpose2021
|
|
145
147
|
type?: StatusListType | 'StatusList2021Entry'
|
|
146
148
|
id?: string
|
|
147
149
|
statusListIndex: string | number
|
|
148
|
-
}): Promise<
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
getAssertedValue('issuer', issuer) // We are only checking the value here
|
|
153
|
-
getAssertedValue('credentialSubject', credentialSubject)
|
|
154
|
-
if (args.statusPurpose && 'statusPurpose' in credentialSubject) {
|
|
155
|
-
if (args.statusPurpose !== credentialSubject.statusPurpose) {
|
|
156
|
-
throw Error(
|
|
157
|
-
`Status purpose in StatusList credential with id ${id} and value ${credentialSubject.statusPurpose} does not match supplied purpose: ${args.statusPurpose}`,
|
|
158
|
-
)
|
|
159
|
-
}
|
|
160
|
-
} else if (args.id && args.id !== id) {
|
|
161
|
-
throw Error(`Status list id ${id} did not match required supplied id: ${args.id}`)
|
|
162
|
-
}
|
|
163
|
-
if (!type || !(type.includes(requestedType) || type.includes(requestedType + 'Credential'))) {
|
|
164
|
-
throw Error(`Credential type ${JSON.stringify(type)} does not contain requested type ${requestedType}`)
|
|
165
|
-
}
|
|
166
|
-
// @ts-ignore
|
|
167
|
-
const encodedList = getAssertedValue('encodedList', credentialSubject['encodedList'])
|
|
168
|
-
|
|
169
|
-
const statusList = await StatusList.decode({ encodedList })
|
|
170
|
-
const status = statusList.getStatus(typeof args.statusListIndex === 'number' ? args.statusListIndex : Number.parseInt(args.statusListIndex))
|
|
171
|
-
return status
|
|
150
|
+
}): Promise<number | Status2021 | StatusOAuth> {
|
|
151
|
+
const statusListType: StatusListType = determineStatusListType(args.statusListCredential)
|
|
152
|
+
const implementation = getStatusListImplementation(statusListType)
|
|
153
|
+
return implementation.checkStatusIndex(args)
|
|
172
154
|
}
|
|
173
155
|
|
|
174
156
|
export async function createNewStatusList(
|
|
175
157
|
args: CreateNewStatusListFuncArgs,
|
|
176
158
|
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
177
159
|
): Promise<StatusListResult> {
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
const correlationId = getAssertedValue('correlationId', args.correlationId)
|
|
182
|
-
|
|
183
|
-
const list = new StatusList({ length })
|
|
184
|
-
const encodedList = await list.encode()
|
|
185
|
-
const statusPurpose = args.statusPurpose ?? 'revocation'
|
|
186
|
-
const statusListCredential = await statusList2021ToVerifiableCredential(
|
|
187
|
-
{
|
|
188
|
-
...args,
|
|
189
|
-
type,
|
|
190
|
-
proofFormat,
|
|
191
|
-
encodedList,
|
|
192
|
-
},
|
|
193
|
-
context,
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
return {
|
|
197
|
-
encodedList,
|
|
198
|
-
statusListCredential,
|
|
199
|
-
length,
|
|
200
|
-
type,
|
|
201
|
-
proofFormat,
|
|
202
|
-
id,
|
|
203
|
-
correlationId,
|
|
204
|
-
issuer,
|
|
205
|
-
statusPurpose,
|
|
206
|
-
indexingDirection: 'rightToLeft',
|
|
207
|
-
} as StatusListResult
|
|
160
|
+
const { type } = getAssertedValues(args)
|
|
161
|
+
const implementation = getStatusListImplementation(type)
|
|
162
|
+
return implementation.createNewStatusList(args, context)
|
|
208
163
|
}
|
|
209
164
|
|
|
210
165
|
export async function updateStatusIndexFromStatusListCredential(
|
|
211
|
-
args:
|
|
166
|
+
args: UpdateStatusListIndexArgs,
|
|
212
167
|
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
213
|
-
): Promise<
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
value: args.value,
|
|
219
|
-
},
|
|
220
|
-
context,
|
|
221
|
-
)
|
|
168
|
+
): Promise<StatusListResult> {
|
|
169
|
+
const credential = getAssertedValue('statusListCredential', args.statusListCredential)
|
|
170
|
+
const statusListType: StatusListType = determineStatusListType(credential)
|
|
171
|
+
const implementation = getStatusListImplementation(statusListType)
|
|
172
|
+
return implementation.updateStatusListIndex(args, context)
|
|
222
173
|
}
|
|
223
174
|
|
|
175
|
+
// Keeping helper function for backward compatibility
|
|
224
176
|
export async function statusListCredentialToDetails(args: {
|
|
225
|
-
statusListCredential:
|
|
177
|
+
statusListCredential: StatusListCredential
|
|
226
178
|
correlationId?: string
|
|
227
179
|
driverType?: StatusListDriverType
|
|
228
|
-
}): Promise<
|
|
180
|
+
}): Promise<StatusListResult> {
|
|
229
181
|
const credential = getAssertedValue('statusListCredential', args.statusListCredential)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
182
|
+
|
|
183
|
+
let statusListType: StatusListType | undefined
|
|
184
|
+
const documentFormat = CredentialMapper.detectDocumentType(credential)
|
|
185
|
+
if (documentFormat === DocumentFormat.JWT) {
|
|
186
|
+
const [header] = credential.split('.')
|
|
187
|
+
const decodedHeader = JSON.parse(Buffer.from(header, 'base64').toString())
|
|
188
|
+
|
|
189
|
+
if (decodedHeader.typ === 'statuslist+jwt') {
|
|
190
|
+
statusListType = StatusListType.OAuthStatusList
|
|
191
|
+
}
|
|
192
|
+
} else if (documentFormat === DocumentFormat.MSO_MDOC) {
|
|
193
|
+
statusListType = StatusListType.OAuthStatusList
|
|
194
|
+
// TODO check CBOR content?
|
|
234
195
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
issuer,
|
|
243
|
-
type: StatusListType.StatusList2021,
|
|
244
|
-
proofFormat,
|
|
245
|
-
indexingDirection: 'rightToLeft',
|
|
246
|
-
length: (await StatusList.decode({ encodedList })).length,
|
|
247
|
-
statusPurpose,
|
|
248
|
-
statusListCredential: credential,
|
|
249
|
-
...(args.correlationId && { correlationId: args.correlationId }),
|
|
250
|
-
...(args.driverType && { driverType: args.driverType }),
|
|
196
|
+
if (!statusListType) {
|
|
197
|
+
const uniform = CredentialMapper.toUniformCredential(credential)
|
|
198
|
+
const type = uniform.type.find((t) => t.includes('StatusList2021') || t.includes('OAuth2StatusList'))
|
|
199
|
+
if (!type) {
|
|
200
|
+
throw new Error('Invalid status list credential type')
|
|
201
|
+
}
|
|
202
|
+
statusListType = type.replace('Credential', '') as StatusListType
|
|
251
203
|
}
|
|
204
|
+
|
|
205
|
+
const implementation = getStatusListImplementation(statusListType)
|
|
206
|
+
return await implementation.toStatusListDetails({
|
|
207
|
+
statusListPayload: credential,
|
|
208
|
+
correlationId: args.correlationId,
|
|
209
|
+
driverType: args.driverType,
|
|
210
|
+
})
|
|
252
211
|
}
|
|
253
212
|
|
|
254
213
|
export async function updateStatusListIndexFromEncodedList(
|
|
255
214
|
args: UpdateStatusListFromEncodedListArgs,
|
|
256
215
|
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
257
|
-
): Promise<
|
|
258
|
-
const {
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
const index = getAssertedValue('index', typeof args.statusListIndex === 'number' ? args.statusListIndex : Number.parseInt(args.statusListIndex))
|
|
262
|
-
const value = getAssertedValue('value', args.value)
|
|
263
|
-
const statusPurpose = getAssertedValue('statusPurpose', args.statusPurpose)
|
|
264
|
-
|
|
265
|
-
const statusList = await StatusList.decode({ encodedList: origEncodedList })
|
|
266
|
-
statusList.setStatus(index, value)
|
|
267
|
-
const encodedList = await statusList.encode()
|
|
268
|
-
const statusListCredential = await statusList2021ToVerifiableCredential(
|
|
269
|
-
{
|
|
270
|
-
...args,
|
|
271
|
-
type,
|
|
272
|
-
proofFormat,
|
|
273
|
-
encodedList,
|
|
274
|
-
},
|
|
275
|
-
context,
|
|
276
|
-
)
|
|
277
|
-
return {
|
|
278
|
-
encodedList,
|
|
279
|
-
statusListCredential,
|
|
280
|
-
length: statusList.length - 1,
|
|
281
|
-
type,
|
|
282
|
-
proofFormat,
|
|
283
|
-
id,
|
|
284
|
-
issuer,
|
|
285
|
-
statusPurpose,
|
|
286
|
-
indexingDirection: 'rightToLeft',
|
|
287
|
-
}
|
|
216
|
+
): Promise<StatusListResult> {
|
|
217
|
+
const { type } = getAssertedValue('type', args)
|
|
218
|
+
const implementation = getStatusListImplementation(type!)
|
|
219
|
+
return implementation.updateStatusListFromEncodedList(args, context)
|
|
288
220
|
}
|
|
289
221
|
|
|
290
222
|
export async function statusList2021ToVerifiableCredential(
|
|
291
223
|
args: StatusList2021ToVerifiableCredentialArgs,
|
|
292
224
|
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
293
|
-
): Promise<
|
|
225
|
+
): Promise<StatusListCredential> {
|
|
294
226
|
const { issuer, id, type } = getAssertedValues(args)
|
|
295
227
|
const identifier = await context.agent.identifierManagedGet({
|
|
296
228
|
identifier: typeof issuer === 'string' ? issuer : issuer.id,
|
|
297
229
|
vmRelationship: 'assertionMethod',
|
|
298
230
|
offlineWhenNoDIDRegistered: true, // FIXME Fix identifier resolution for EBSI
|
|
299
231
|
})
|
|
232
|
+
const proofFormat: ProofFormat = args?.proofFormat ?? 'lds'
|
|
233
|
+
assertValidProofType(StatusListType.StatusList2021, proofFormat)
|
|
234
|
+
const veramoProofFormat: VeramoProofFormat = proofFormat as VeramoProofFormat
|
|
235
|
+
|
|
300
236
|
const encodedList = getAssertedValue('encodedList', args.encodedList)
|
|
301
237
|
const statusPurpose = getAssertedValue('statusPurpose', args.statusPurpose)
|
|
302
238
|
const credential = {
|
|
@@ -316,31 +252,9 @@ export async function statusList2021ToVerifiableCredential(
|
|
|
316
252
|
const verifiableCredential = await context.agent.createVerifiableCredential({
|
|
317
253
|
credential,
|
|
318
254
|
keyRef: identifier.kmsKeyRef,
|
|
319
|
-
proofFormat:
|
|
255
|
+
proofFormat: veramoProofFormat,
|
|
320
256
|
fetchRemoteContexts: true,
|
|
321
257
|
})
|
|
322
258
|
|
|
323
|
-
return CredentialMapper.toWrappedVerifiableCredential(verifiableCredential as
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function getAssertedStatusListType(type?: StatusListType) {
|
|
327
|
-
const assertedType = type ?? StatusListType.StatusList2021
|
|
328
|
-
if (assertedType !== StatusListType.StatusList2021) {
|
|
329
|
-
throw Error(`StatusList type ${assertedType} is not supported (yet)`)
|
|
330
|
-
}
|
|
331
|
-
return assertedType
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function getAssertedValue<T>(name: string, value: T): NonNullable<T> {
|
|
335
|
-
if (value === undefined || value === null) {
|
|
336
|
-
throw Error(`Missing required ${name} value`)
|
|
337
|
-
}
|
|
338
|
-
return value
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function getAssertedValues(args: { issuer: string | IIssuer; id: string; type?: StatusListType }) {
|
|
342
|
-
const type = getAssertedStatusListType(args?.type)
|
|
343
|
-
const id = getAssertedValue('id', args.id)
|
|
344
|
-
const issuer = getAssertedValue('issuer', args.issuer)
|
|
345
|
-
return { id, issuer, type }
|
|
259
|
+
return CredentialMapper.toWrappedVerifiableCredential(verifiableCredential as StatusListCredential).original as StatusListCredential
|
|
346
260
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { IAgentContext, ICredentialPlugin } from '@veramo/core'
|
|
2
|
+
import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
3
|
+
import {
|
|
4
|
+
CheckStatusIndexArgs,
|
|
5
|
+
CreateStatusListArgs,
|
|
6
|
+
Status2021,
|
|
7
|
+
StatusListResult,
|
|
8
|
+
StatusOAuth,
|
|
9
|
+
ToStatusListDetailsArgs,
|
|
10
|
+
UpdateStatusListFromEncodedListArgs,
|
|
11
|
+
UpdateStatusListIndexArgs,
|
|
12
|
+
} from '../types'
|
|
13
|
+
|
|
14
|
+
export interface IStatusList {
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new status list of the specific type
|
|
17
|
+
*/
|
|
18
|
+
createNewStatusList(args: CreateStatusListArgs, context: IAgentContext<ICredentialPlugin & IIdentifierResolution>): Promise<StatusListResult>
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Updates a status at the given index in the status list
|
|
22
|
+
*/
|
|
23
|
+
updateStatusListIndex(args: UpdateStatusListIndexArgs, context: IAgentContext<ICredentialPlugin & IIdentifierResolution>): Promise<StatusListResult>
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Updates a status list using a base64 encoded list of statuses
|
|
27
|
+
*/
|
|
28
|
+
updateStatusListFromEncodedList(
|
|
29
|
+
args: UpdateStatusListFromEncodedListArgs,
|
|
30
|
+
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
31
|
+
): Promise<StatusListResult>
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Checks the status at a given index in the status list
|
|
35
|
+
*/
|
|
36
|
+
checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | Status2021 | StatusOAuth>
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Collects the status list details
|
|
40
|
+
*/
|
|
41
|
+
toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult>
|
|
42
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { IAgentContext, ICredentialPlugin, IKeyManager } from '@veramo/core'
|
|
2
|
+
import { CompactJWT, CWT, ProofFormat, StatusListType } from '@sphereon/ssi-types'
|
|
3
|
+
import {
|
|
4
|
+
CheckStatusIndexArgs,
|
|
5
|
+
CreateStatusListArgs,
|
|
6
|
+
SignedStatusListData,
|
|
7
|
+
StatusListResult,
|
|
8
|
+
StatusOAuth,
|
|
9
|
+
ToStatusListDetailsArgs,
|
|
10
|
+
UpdateStatusListFromEncodedListArgs,
|
|
11
|
+
UpdateStatusListIndexArgs,
|
|
12
|
+
} from '../types'
|
|
13
|
+
import { determineProofFormat, getAssertedValue, getAssertedValues } from '../utils'
|
|
14
|
+
import { IStatusList } from './IStatusList'
|
|
15
|
+
import { StatusList } from '@sd-jwt/jwt-status-list'
|
|
16
|
+
import { IJwtService } from '@sphereon/ssi-sdk-ext.jwt-service'
|
|
17
|
+
import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
18
|
+
import { createSignedJwt, decodeStatusListJWT } from './encoding/jwt'
|
|
19
|
+
import { createSignedCbor, decodeStatusListCWT } from './encoding/cbor'
|
|
20
|
+
|
|
21
|
+
type IRequiredContext = IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_BITS_PER_STATUS = 2 // 2 bits are sufficient for 0x00 - "VALID" 0x01 - "INVALID" & 0x02 - "SUSPENDED"
|
|
24
|
+
export const DEFAULT_LIST_LENGTH = 250000
|
|
25
|
+
export const DEFAULT_PROOF_FORMAT = 'jwt' as ProofFormat
|
|
26
|
+
|
|
27
|
+
export class OAuthStatusListImplementation implements IStatusList {
|
|
28
|
+
async createNewStatusList(args: CreateStatusListArgs, context: IRequiredContext): Promise<StatusListResult> {
|
|
29
|
+
if (!args.oauthStatusList) {
|
|
30
|
+
throw new Error('OAuthStatusList options are required for type OAuthStatusList')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const proofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
|
|
34
|
+
const { issuer, id, oauthStatusList, keyRef } = args
|
|
35
|
+
const { bitsPerStatus, expiresAt } = oauthStatusList
|
|
36
|
+
const length = args.length ?? DEFAULT_LIST_LENGTH
|
|
37
|
+
const issuerString = typeof issuer === 'string' ? issuer : issuer.id
|
|
38
|
+
const correlationId = getAssertedValue('correlationId', args.correlationId)
|
|
39
|
+
|
|
40
|
+
const statusList = new StatusList(new Array(length).fill(0), bitsPerStatus ?? DEFAULT_BITS_PER_STATUS)
|
|
41
|
+
const encodedList = statusList.compressStatusList()
|
|
42
|
+
const { statusListCredential } = await this.createSignedStatusList(proofFormat, context, statusList, issuerString, id, expiresAt, keyRef)
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
encodedList,
|
|
46
|
+
statusListCredential,
|
|
47
|
+
oauthStatusList: { bitsPerStatus },
|
|
48
|
+
length,
|
|
49
|
+
type: StatusListType.OAuthStatusList,
|
|
50
|
+
proofFormat,
|
|
51
|
+
id,
|
|
52
|
+
correlationId,
|
|
53
|
+
issuer,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async updateStatusListIndex(args: UpdateStatusListIndexArgs, context: IRequiredContext): Promise<StatusListResult> {
|
|
58
|
+
const { statusListCredential, value, expiresAt, keyRef } = args
|
|
59
|
+
if (typeof statusListCredential !== 'string') {
|
|
60
|
+
return Promise.reject('statusListCredential in neither JWT nor CWT')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const proofFormat = determineProofFormat(statusListCredential)
|
|
64
|
+
const decoded = proofFormat === 'jwt' ? decodeStatusListJWT(statusListCredential) : decodeStatusListCWT(statusListCredential)
|
|
65
|
+
const { statusList, issuer, id } = decoded
|
|
66
|
+
|
|
67
|
+
const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
68
|
+
if (index < 0 || index >= statusList.statusList.length) {
|
|
69
|
+
throw new Error('Status list index out of bounds')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
statusList.setStatus(index, value)
|
|
73
|
+
const { statusListCredential: signedCredential, encodedList } = await this.createSignedStatusList(
|
|
74
|
+
proofFormat,
|
|
75
|
+
context,
|
|
76
|
+
statusList,
|
|
77
|
+
issuer,
|
|
78
|
+
id,
|
|
79
|
+
expiresAt,
|
|
80
|
+
keyRef,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
statusListCredential: signedCredential,
|
|
85
|
+
encodedList,
|
|
86
|
+
oauthStatusList: {
|
|
87
|
+
bitsPerStatus: statusList.getBitsPerStatus(),
|
|
88
|
+
},
|
|
89
|
+
length: statusList.statusList.length,
|
|
90
|
+
type: StatusListType.OAuthStatusList,
|
|
91
|
+
proofFormat,
|
|
92
|
+
id,
|
|
93
|
+
issuer,
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async updateStatusListFromEncodedList(args: UpdateStatusListFromEncodedListArgs, context: IRequiredContext): Promise<StatusListResult> {
|
|
98
|
+
if (!args.oauthStatusList) {
|
|
99
|
+
throw new Error('OAuthStatusList options are required for type OAuthStatusList')
|
|
100
|
+
}
|
|
101
|
+
const { proofFormat, oauthStatusList, keyRef } = args
|
|
102
|
+
const { bitsPerStatus, expiresAt } = oauthStatusList
|
|
103
|
+
|
|
104
|
+
const { issuer, id } = getAssertedValues(args)
|
|
105
|
+
const issuerString = typeof issuer === 'string' ? issuer : issuer.id
|
|
106
|
+
|
|
107
|
+
const listToUpdate = StatusList.decompressStatusList(args.encodedList, bitsPerStatus ?? DEFAULT_BITS_PER_STATUS)
|
|
108
|
+
const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
109
|
+
listToUpdate.setStatus(index, args.value ? 1 : 0)
|
|
110
|
+
|
|
111
|
+
const { statusListCredential, encodedList } = await this.createSignedStatusList(
|
|
112
|
+
proofFormat ?? DEFAULT_PROOF_FORMAT,
|
|
113
|
+
context,
|
|
114
|
+
listToUpdate,
|
|
115
|
+
issuerString,
|
|
116
|
+
id,
|
|
117
|
+
expiresAt,
|
|
118
|
+
keyRef,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
encodedList,
|
|
123
|
+
statusListCredential,
|
|
124
|
+
oauthStatusList: {
|
|
125
|
+
bitsPerStatus,
|
|
126
|
+
expiresAt,
|
|
127
|
+
},
|
|
128
|
+
length: listToUpdate.statusList.length,
|
|
129
|
+
type: StatusListType.OAuthStatusList,
|
|
130
|
+
proofFormat: proofFormat ?? DEFAULT_PROOF_FORMAT,
|
|
131
|
+
id,
|
|
132
|
+
issuer,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | StatusOAuth> {
|
|
137
|
+
const { statusListCredential, statusListIndex } = args
|
|
138
|
+
if (typeof statusListCredential !== 'string') {
|
|
139
|
+
return Promise.reject('statusListCredential in neither JWT nor CWT')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const proofFormat = determineProofFormat(statusListCredential)
|
|
143
|
+
const { statusList } = proofFormat === 'jwt' ? decodeStatusListJWT(statusListCredential) : decodeStatusListCWT(statusListCredential)
|
|
144
|
+
|
|
145
|
+
const index = typeof statusListIndex === 'number' ? statusListIndex : parseInt(statusListIndex)
|
|
146
|
+
if (index < 0 || index >= statusList.statusList.length) {
|
|
147
|
+
throw new Error('Status list index out of bounds')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return statusList.getStatus(index)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult> {
|
|
154
|
+
const { statusListPayload } = args as { statusListPayload: CompactJWT | CWT }
|
|
155
|
+
const proofFormat = determineProofFormat(statusListPayload)
|
|
156
|
+
const decoded = proofFormat === 'jwt' ? decodeStatusListJWT(statusListPayload) : decodeStatusListCWT(statusListPayload)
|
|
157
|
+
const { statusList, issuer, id, exp } = decoded
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
id,
|
|
161
|
+
encodedList: statusList.compressStatusList(),
|
|
162
|
+
issuer,
|
|
163
|
+
type: StatusListType.OAuthStatusList,
|
|
164
|
+
proofFormat,
|
|
165
|
+
length: statusList.statusList.length,
|
|
166
|
+
statusListCredential: statusListPayload,
|
|
167
|
+
oauthStatusList: {
|
|
168
|
+
bitsPerStatus: statusList.getBitsPerStatus(),
|
|
169
|
+
...(exp && { expiresAt: new Date(exp * 1000) }),
|
|
170
|
+
},
|
|
171
|
+
...(args.correlationId && { correlationId: args.correlationId }),
|
|
172
|
+
...(args.driverType && { driverType: args.driverType }),
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private async createSignedStatusList(
|
|
177
|
+
proofFormat: 'jwt' | 'lds' | 'EthereumEip712Signature2021' | 'cbor',
|
|
178
|
+
context: IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>,
|
|
179
|
+
statusList: StatusList,
|
|
180
|
+
issuerString: string,
|
|
181
|
+
id: string,
|
|
182
|
+
expiresAt?: Date,
|
|
183
|
+
keyRef?: string,
|
|
184
|
+
): Promise<SignedStatusListData> {
|
|
185
|
+
switch (proofFormat) {
|
|
186
|
+
case 'jwt': {
|
|
187
|
+
return await createSignedJwt(context, statusList, issuerString, id, expiresAt, keyRef)
|
|
188
|
+
}
|
|
189
|
+
case 'cbor': {
|
|
190
|
+
return await createSignedCbor(context, statusList, issuerString, id, expiresAt, keyRef)
|
|
191
|
+
}
|
|
192
|
+
default:
|
|
193
|
+
throw new Error(`Invalid proof format '${proofFormat}' for OAuthStatusList`)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|