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