@sphereon/ssi-sdk.vc-status-list 0.32.1-feature.MWALL.715.49 → 0.32.1-feature.SDK.56.oauth.status.list.34
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 +40 -109
- package/dist/functions.js.map +1 -1
- package/dist/impl/IStatusList.d.ts +22 -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 +14 -0
- package/dist/impl/StatusList2021.d.ts.map +1 -0
- package/dist/impl/StatusList2021.js +153 -0
- package/dist/impl/StatusList2021.js.map +1 -0
- package/dist/impl/StatusListFactory.d.ts +12 -0
- package/dist/impl/StatusListFactory.d.ts.map +1 -0
- package/dist/impl/StatusListFactory.js +36 -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 +128 -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 +7 -0
- package/dist/impl/encoding/jwt.d.ts.map +1 -0
- package/dist/impl/encoding/jwt.js +56 -0
- package/dist/impl/encoding/jwt.js.map +1 -0
- package/dist/types/index.d.ts +107 -29
- 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 +87 -0
- package/dist/utils.js.map +1 -0
- package/package.json +13 -5
- package/src/functions.ts +59 -158
- package/src/impl/IStatusList.ts +36 -0
- package/src/impl/OAuthStatusList.ts +165 -0
- package/src/impl/StatusList2021.ts +195 -0
- package/src/impl/StatusListFactory.ts +39 -0
- package/src/impl/encoding/cbor.ts +152 -0
- package/src/impl/encoding/common.ts +25 -0
- package/src/impl/encoding/jwt.ts +54 -0
- package/src/types/index.ts +123 -41
- package/src/utils.ts +91 -0
package/src/functions.ts
CHANGED
|
@@ -1,41 +1,42 @@
|
|
|
1
1
|
import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
2
2
|
import {
|
|
3
3
|
CredentialMapper,
|
|
4
|
-
|
|
5
|
-
IIssuer,
|
|
6
|
-
OriginalVerifiableCredential,
|
|
4
|
+
ProofFormat,
|
|
7
5
|
StatusListDriverType,
|
|
8
6
|
StatusListType,
|
|
7
|
+
StatusListVerifiableCredential,
|
|
9
8
|
StatusPurpose2021,
|
|
10
9
|
} from '@sphereon/ssi-types'
|
|
10
|
+
import { CredentialStatus, DIDDocument, IAgentContext, ICredentialPlugin, ProofFormat as VmoProofFormat } from '@veramo/core'
|
|
11
11
|
|
|
12
|
-
import { checkStatus
|
|
13
|
-
import { CredentialStatus, DIDDocument, IAgentContext, ICredentialPlugin, ProofFormat } from '@veramo/core'
|
|
12
|
+
import { checkStatus } from '@sphereon/vc-status-list'
|
|
14
13
|
import { CredentialJwtOrJSON, StatusMethod } from 'credential-status'
|
|
15
14
|
import {
|
|
16
15
|
CreateNewStatusListFuncArgs,
|
|
16
|
+
Status2021,
|
|
17
17
|
StatusList2021ToVerifiableCredentialArgs,
|
|
18
|
-
StatusListDetails,
|
|
19
18
|
StatusListResult,
|
|
19
|
+
StatusOAuth,
|
|
20
20
|
UpdateStatusListFromEncodedListArgs,
|
|
21
21
|
UpdateStatusListFromStatusListCredentialArgs,
|
|
22
22
|
} from './types'
|
|
23
|
+
import { assertValidProofType, determineStatusListType, getAssertedValue, getAssertedValues } from './utils'
|
|
24
|
+
import { getStatusListImplementation } from './impl/StatusListFactory'
|
|
23
25
|
|
|
24
|
-
export async function fetchStatusListCredential(args: { statusListCredential: string }): Promise<
|
|
26
|
+
export async function fetchStatusListCredential(args: { statusListCredential: string }): Promise<StatusListVerifiableCredential> {
|
|
25
27
|
const url = getAssertedValue('statusListCredential', args.statusListCredential)
|
|
26
28
|
try {
|
|
27
29
|
const response = await fetch(url)
|
|
28
30
|
if (!response.ok) {
|
|
29
|
-
|
|
30
|
-
throw Error(error)
|
|
31
|
+
throw Error(`Fetching status list ${url} resulted in an error: ${response.status} : ${response.statusText}`)
|
|
31
32
|
}
|
|
32
33
|
const responseAsText = await response.text()
|
|
33
34
|
if (responseAsText.trim().startsWith('{')) {
|
|
34
|
-
return JSON.parse(responseAsText) as
|
|
35
|
+
return JSON.parse(responseAsText) as StatusListVerifiableCredential
|
|
35
36
|
}
|
|
36
|
-
return responseAsText as
|
|
37
|
+
return responseAsText as StatusListVerifiableCredential
|
|
37
38
|
} catch (error) {
|
|
38
|
-
console.
|
|
39
|
+
console.error(`Fetching status list ${url} resulted in an unexpected error: ${error instanceof Error ? error.message : JSON.stringify(error)}`)
|
|
39
40
|
throw error
|
|
40
41
|
}
|
|
41
42
|
}
|
|
@@ -52,7 +53,7 @@ export function statusPluginStatusFunction(args: {
|
|
|
52
53
|
const result = await checkStatusForCredential({
|
|
53
54
|
...args,
|
|
54
55
|
documentLoader: args.documentLoader,
|
|
55
|
-
credential: credential as
|
|
56
|
+
credential: credential as StatusListVerifiableCredential,
|
|
56
57
|
errorUnknownListType: args.errorUnknownListType,
|
|
57
58
|
})
|
|
58
59
|
|
|
@@ -75,7 +76,7 @@ export function vcLibCheckStatusFunction(args: {
|
|
|
75
76
|
}) {
|
|
76
77
|
const { mandatoryCredentialStatus, verifyStatusListCredential, verifyMatchingIssuers, errorUnknownListType } = args
|
|
77
78
|
return (args: {
|
|
78
|
-
credential:
|
|
79
|
+
credential: StatusListVerifiableCredential
|
|
79
80
|
documentLoader: any
|
|
80
81
|
suite: any
|
|
81
82
|
}): Promise<{
|
|
@@ -93,7 +94,7 @@ export function vcLibCheckStatusFunction(args: {
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
export async function checkStatusForCredential(args: {
|
|
96
|
-
credential:
|
|
97
|
+
credential: StatusListVerifiableCredential
|
|
97
98
|
documentLoader: any
|
|
98
99
|
suite: any
|
|
99
100
|
mandatoryCredentialStatus?: boolean
|
|
@@ -132,7 +133,7 @@ export async function simpleCheckStatusFromStatusListUrl(args: {
|
|
|
132
133
|
type?: StatusListType | 'StatusList2021Entry'
|
|
133
134
|
id?: string
|
|
134
135
|
statusListIndex: string
|
|
135
|
-
}): Promise<
|
|
136
|
+
}): Promise<number | Status2021 | StatusOAuth> {
|
|
136
137
|
return checkStatusIndexFromStatusListCredential({
|
|
137
138
|
...args,
|
|
138
139
|
statusListCredential: await fetchStatusListCredential(args),
|
|
@@ -140,163 +141,84 @@ export async function simpleCheckStatusFromStatusListUrl(args: {
|
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
export async function checkStatusIndexFromStatusListCredential(args: {
|
|
143
|
-
statusListCredential:
|
|
144
|
+
statusListCredential: StatusListVerifiableCredential
|
|
144
145
|
statusPurpose?: StatusPurpose2021
|
|
145
146
|
type?: StatusListType | 'StatusList2021Entry'
|
|
146
147
|
id?: string
|
|
147
148
|
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
|
|
149
|
+
}): Promise<number | Status2021 | StatusOAuth> {
|
|
150
|
+
const statusListType: StatusListType = determineStatusListType(args.statusListCredential)
|
|
151
|
+
const implementation = getStatusListImplementation(statusListType)
|
|
152
|
+
return implementation.checkStatusIndex(args)
|
|
172
153
|
}
|
|
173
154
|
|
|
174
155
|
export async function createNewStatusList(
|
|
175
156
|
args: CreateNewStatusListFuncArgs,
|
|
176
157
|
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
177
158
|
): 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
|
|
159
|
+
const { type } = getAssertedValues(args)
|
|
160
|
+
const implementation = getStatusListImplementation(type)
|
|
161
|
+
return implementation.createNewStatusList(args, context)
|
|
208
162
|
}
|
|
209
163
|
|
|
210
164
|
export async function updateStatusIndexFromStatusListCredential(
|
|
211
165
|
args: UpdateStatusListFromStatusListCredentialArgs,
|
|
212
166
|
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
213
|
-
): Promise<
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
value: args.value,
|
|
219
|
-
},
|
|
220
|
-
context,
|
|
221
|
-
)
|
|
167
|
+
): Promise<StatusListResult> {
|
|
168
|
+
const credential = getAssertedValue('statusListCredential', args.statusListCredential)
|
|
169
|
+
const statusListType: StatusListType = determineStatusListType(credential)
|
|
170
|
+
const implementation = getStatusListImplementation(statusListType)
|
|
171
|
+
return implementation.updateStatusListIndex(args, context)
|
|
222
172
|
}
|
|
223
173
|
|
|
174
|
+
// Keeping helper function for backward compatibility
|
|
224
175
|
export async function statusListCredentialToDetails(args: {
|
|
225
|
-
statusListCredential:
|
|
176
|
+
statusListCredential: StatusListVerifiableCredential
|
|
226
177
|
correlationId?: string
|
|
227
178
|
driverType?: StatusListDriverType
|
|
228
|
-
}): Promise<
|
|
179
|
+
}): Promise<StatusListResult> {
|
|
229
180
|
const credential = getAssertedValue('statusListCredential', args.statusListCredential)
|
|
230
181
|
const uniform = CredentialMapper.toUniformCredential(credential)
|
|
231
|
-
const
|
|
232
|
-
if (!type
|
|
233
|
-
throw Error('
|
|
234
|
-
}
|
|
235
|
-
const id = getAssertedValue('id', uniform.id)
|
|
236
|
-
// @ts-ignore
|
|
237
|
-
const { encodedList, statusPurpose } = credentialSubject
|
|
238
|
-
const proofFormat: ProofFormat = CredentialMapper.detectDocumentType(credential) === DocumentFormat.JWT ? 'jwt' : 'lds'
|
|
239
|
-
return {
|
|
240
|
-
id,
|
|
241
|
-
encodedList,
|
|
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 }),
|
|
182
|
+
const type = uniform.type.find((t) => t.includes('StatusList2021') || t.includes('OAuth2StatusList'))
|
|
183
|
+
if (!type) {
|
|
184
|
+
throw new Error('Invalid status list credential type')
|
|
251
185
|
}
|
|
186
|
+
const statusListType = type.replace('Credential', '') as StatusListType
|
|
187
|
+
const implementation = getStatusListImplementation(statusListType)
|
|
188
|
+
return implementation.updateStatusListIndex(
|
|
189
|
+
{
|
|
190
|
+
statusListCredential: args.statusListCredential,
|
|
191
|
+
statusListIndex: 0,
|
|
192
|
+
value: 0,
|
|
193
|
+
},
|
|
194
|
+
{} as IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
195
|
+
)
|
|
252
196
|
}
|
|
253
197
|
|
|
254
198
|
export async function updateStatusListIndexFromEncodedList(
|
|
255
199
|
args: UpdateStatusListFromEncodedListArgs,
|
|
256
200
|
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
|
-
}
|
|
201
|
+
): Promise<StatusListResult> {
|
|
202
|
+
const { type } = getAssertedValue('type', args)
|
|
203
|
+
const implementation = getStatusListImplementation(type!)
|
|
204
|
+
return implementation.updateStatusListFromEncodedList(args, context)
|
|
288
205
|
}
|
|
289
206
|
|
|
207
|
+
// TODO Is this still in use? Or do we need to redesign this after having multiple status list types?
|
|
290
208
|
export async function statusList2021ToVerifiableCredential(
|
|
291
209
|
args: StatusList2021ToVerifiableCredentialArgs,
|
|
292
210
|
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
293
|
-
): Promise<
|
|
211
|
+
): Promise<StatusListVerifiableCredential> {
|
|
294
212
|
const { issuer, id, type } = getAssertedValues(args)
|
|
295
213
|
const identifier = await context.agent.identifierManagedGet({
|
|
296
214
|
identifier: typeof issuer === 'string' ? issuer : issuer.id,
|
|
297
215
|
vmRelationship: 'assertionMethod',
|
|
298
216
|
offlineWhenNoDIDRegistered: true, // FIXME Fix identifier resolution for EBSI
|
|
299
217
|
})
|
|
218
|
+
const proofFormat: ProofFormat = args?.proofFormat ?? 'lds'
|
|
219
|
+
assertValidProofType(StatusListType.StatusList2021, proofFormat)
|
|
220
|
+
const vmoProofFormat: VmoProofFormat = proofFormat as VmoProofFormat
|
|
221
|
+
|
|
300
222
|
const encodedList = getAssertedValue('encodedList', args.encodedList)
|
|
301
223
|
const statusPurpose = getAssertedValue('statusPurpose', args.statusPurpose)
|
|
302
224
|
const credential = {
|
|
@@ -316,31 +238,10 @@ export async function statusList2021ToVerifiableCredential(
|
|
|
316
238
|
const verifiableCredential = await context.agent.createVerifiableCredential({
|
|
317
239
|
credential,
|
|
318
240
|
keyRef: identifier.kmsKeyRef,
|
|
319
|
-
proofFormat:
|
|
241
|
+
proofFormat: vmoProofFormat,
|
|
320
242
|
fetchRemoteContexts: true,
|
|
321
243
|
})
|
|
322
244
|
|
|
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 }
|
|
245
|
+
return CredentialMapper.toWrappedVerifiableCredential(verifiableCredential as StatusListVerifiableCredential)
|
|
246
|
+
.original as StatusListVerifiableCredential
|
|
346
247
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
UpdateStatusListFromEncodedListArgs,
|
|
10
|
+
UpdateStatusListIndexArgs,
|
|
11
|
+
} from '../types'
|
|
12
|
+
|
|
13
|
+
export interface IStatusList {
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new status list of the specific type
|
|
16
|
+
*/
|
|
17
|
+
createNewStatusList(args: CreateStatusListArgs, context: IAgentContext<ICredentialPlugin & IIdentifierResolution>): Promise<StatusListResult>
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Updates a status at the given index in the status list
|
|
21
|
+
*/
|
|
22
|
+
updateStatusListIndex(args: UpdateStatusListIndexArgs, context: IAgentContext<ICredentialPlugin & IIdentifierResolution>): Promise<StatusListResult>
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Updates a status list using a base64 encoded list of statuses
|
|
26
|
+
*/
|
|
27
|
+
updateStatusListFromEncodedList(
|
|
28
|
+
args: UpdateStatusListFromEncodedListArgs,
|
|
29
|
+
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
30
|
+
): Promise<StatusListResult>
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Checks the status at a given index in the status list
|
|
34
|
+
*/
|
|
35
|
+
checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | Status2021 | StatusOAuth>
|
|
36
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { IAgentContext, ICredentialPlugin } from '@veramo/core'
|
|
2
|
+
import { ProofFormat, StatusListType, StatusListVerifiableCredential } from '@sphereon/ssi-types'
|
|
3
|
+
import {
|
|
4
|
+
CheckStatusIndexArgs,
|
|
5
|
+
CreateStatusListArgs,
|
|
6
|
+
StatusListResult,
|
|
7
|
+
StatusOAuth,
|
|
8
|
+
UpdateStatusListFromEncodedListArgs,
|
|
9
|
+
UpdateStatusListIndexArgs,
|
|
10
|
+
} from '../types'
|
|
11
|
+
import { determineProofFormat, getAssertedValue, getAssertedValues } from '../utils'
|
|
12
|
+
import { IStatusList } from './IStatusList'
|
|
13
|
+
import { StatusList, StatusListJWTHeaderParameters } from '@sd-jwt/jwt-status-list'
|
|
14
|
+
import { IJwtService } from '@sphereon/ssi-sdk-ext.jwt-service'
|
|
15
|
+
import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
16
|
+
import { createSignedJwt, decodeStatusListJWT } from './encoding/jwt'
|
|
17
|
+
import { createSignedCbor, decodeStatusListCWT } from './encoding/cbor'
|
|
18
|
+
|
|
19
|
+
type IRequiredContext = IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution>
|
|
20
|
+
|
|
21
|
+
export const BITS_PER_STATUS_DEFAULT = 2 // 2 bits are sufficient for 0x00 - "VALID" 0x01 - "INVALID" & 0x02 - "SUSPENDED"
|
|
22
|
+
export const DEFAULT_LIST_LENGTH = 250000
|
|
23
|
+
export const DEFAULT_PROOF_FORMAT = 'jwt' as ProofFormat
|
|
24
|
+
export const STATUS_LIST_JWT_HEADER: StatusListJWTHeaderParameters = {
|
|
25
|
+
alg: 'EdDSA',
|
|
26
|
+
typ: 'statuslist+jwt',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class OAuthStatusListImplementation implements IStatusList {
|
|
30
|
+
async createNewStatusList(args: CreateStatusListArgs, context: IRequiredContext): Promise<StatusListResult> {
|
|
31
|
+
if (!args.oauthStatusList) {
|
|
32
|
+
throw new Error('OAuthStatusList options are required for type OAuthStatusList')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const proofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
|
|
36
|
+
const { issuer, id } = args
|
|
37
|
+
const length = args.length ?? DEFAULT_LIST_LENGTH
|
|
38
|
+
const bitsPerStatus = args.oauthStatusList.bitsPerStatus ?? BITS_PER_STATUS_DEFAULT
|
|
39
|
+
const issuerString = typeof issuer === 'string' ? issuer : issuer.id
|
|
40
|
+
const correlationId = getAssertedValue('correlationId', args.correlationId)
|
|
41
|
+
|
|
42
|
+
const statusList = new StatusList(new Array(length).fill(0), bitsPerStatus)
|
|
43
|
+
const encodedList = statusList.compressStatusList()
|
|
44
|
+
let statusListCredential: StatusListVerifiableCredential
|
|
45
|
+
|
|
46
|
+
switch (proofFormat) {
|
|
47
|
+
case 'jwt': {
|
|
48
|
+
const { statusListCredential: slJwt } = await createSignedJwt(context, statusList, issuerString, id, args.keyRef)
|
|
49
|
+
statusListCredential = slJwt
|
|
50
|
+
break
|
|
51
|
+
}
|
|
52
|
+
case 'cbor': {
|
|
53
|
+
const { statusListCredential: slCbor } = await createSignedCbor(context, statusList, issuerString, id, args.keyRef)
|
|
54
|
+
statusListCredential = slCbor
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
default:
|
|
58
|
+
throw new Error(`Invalid proof format '${proofFormat}' for OAuthStatusList`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
encodedList,
|
|
63
|
+
statusListCredential,
|
|
64
|
+
oauthStatusList: { bitsPerStatus },
|
|
65
|
+
length,
|
|
66
|
+
type: StatusListType.OAuthStatusList,
|
|
67
|
+
proofFormat,
|
|
68
|
+
id,
|
|
69
|
+
correlationId,
|
|
70
|
+
issuer,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async updateStatusListIndex(args: UpdateStatusListIndexArgs, context: IRequiredContext): Promise<StatusListResult> {
|
|
75
|
+
const { statusListCredential, value } = args
|
|
76
|
+
if (typeof statusListCredential !== 'string') {
|
|
77
|
+
return Promise.reject('statusListCredential in neither JWT nor CWT')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const proofFormat = determineProofFormat(statusListCredential)
|
|
81
|
+
const decoded = proofFormat === 'jwt' ? decodeStatusListJWT(statusListCredential) : decodeStatusListCWT(statusListCredential)
|
|
82
|
+
const { statusList, issuer, id } = decoded
|
|
83
|
+
|
|
84
|
+
const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
85
|
+
if (index < 0 || index >= statusList.statusList.length) {
|
|
86
|
+
throw new Error('Status list index out of bounds')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
statusList.setStatus(index, value)
|
|
90
|
+
const result =
|
|
91
|
+
proofFormat === 'jwt'
|
|
92
|
+
? await createSignedJwt(context, statusList, issuer, id, args.keyRef)
|
|
93
|
+
: await createSignedCbor(context, statusList, issuer, id, args.keyRef)
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
...result,
|
|
97
|
+
oauthStatusList: {
|
|
98
|
+
bitsPerStatus: statusList.getBitsPerStatus(),
|
|
99
|
+
},
|
|
100
|
+
length: statusList.statusList.length,
|
|
101
|
+
type: StatusListType.OAuthStatusList,
|
|
102
|
+
proofFormat,
|
|
103
|
+
id,
|
|
104
|
+
issuer,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async updateStatusListFromEncodedList(args: UpdateStatusListFromEncodedListArgs, context: IRequiredContext): Promise<StatusListResult> {
|
|
109
|
+
if (!args.oauthStatusList) {
|
|
110
|
+
throw new Error('OAuthStatusList options are required for type OAuthStatusList')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const proofFormat = args.proofFormat ?? DEFAULT_PROOF_FORMAT
|
|
114
|
+
const { issuer, id } = getAssertedValues(args)
|
|
115
|
+
const bitsPerStatus = args.oauthStatusList.bitsPerStatus ?? BITS_PER_STATUS_DEFAULT
|
|
116
|
+
const issuerString = typeof issuer === 'string' ? issuer : issuer.id
|
|
117
|
+
|
|
118
|
+
const listToUpdate = StatusList.decompressStatusList(args.encodedList, bitsPerStatus)
|
|
119
|
+
const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
120
|
+
listToUpdate.setStatus(index, args.value ? 1 : 0)
|
|
121
|
+
|
|
122
|
+
let result: { statusListCredential: StatusListVerifiableCredential; encodedList: string }
|
|
123
|
+
|
|
124
|
+
switch (proofFormat) {
|
|
125
|
+
case 'jwt':
|
|
126
|
+
result = await createSignedJwt(context, listToUpdate, issuerString, id, args.keyRef)
|
|
127
|
+
break
|
|
128
|
+
case 'cbor':
|
|
129
|
+
result = await createSignedCbor(context, listToUpdate, issuerString, id, args.keyRef)
|
|
130
|
+
break
|
|
131
|
+
default:
|
|
132
|
+
throw new Error(`Invalid proof format '${proofFormat}' for OAuthStatusList`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
encodedList: result.encodedList,
|
|
137
|
+
statusListCredential: result.statusListCredential,
|
|
138
|
+
oauthStatusList: {
|
|
139
|
+
bitsPerStatus,
|
|
140
|
+
},
|
|
141
|
+
length: listToUpdate.statusList.length,
|
|
142
|
+
type: StatusListType.OAuthStatusList,
|
|
143
|
+
proofFormat,
|
|
144
|
+
id,
|
|
145
|
+
issuer,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | StatusOAuth> {
|
|
150
|
+
const { statusListCredential, statusListIndex } = args
|
|
151
|
+
if (typeof statusListCredential !== 'string') {
|
|
152
|
+
return Promise.reject('statusListCredential in neither JWT nor CWT')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const proofFormat = determineProofFormat(statusListCredential)
|
|
156
|
+
const { statusList } = proofFormat === 'jwt' ? decodeStatusListJWT(statusListCredential) : decodeStatusListCWT(statusListCredential)
|
|
157
|
+
|
|
158
|
+
const index = typeof statusListIndex === 'number' ? statusListIndex : parseInt(statusListIndex)
|
|
159
|
+
if (index < 0 || index >= statusList.statusList.length) {
|
|
160
|
+
throw new Error('Status list index out of bounds')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return statusList.getStatus(index)
|
|
164
|
+
}
|
|
165
|
+
}
|