@sphereon/ssi-sdk.vc-status-list 0.32.1-next.54 → 0.33.1-feature.jose.vcdm.55

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.
@@ -0,0 +1,177 @@
1
+ import type { BitsPerStatus } from '@sd-jwt/jwt-status-list'
2
+ import { StatusList } from '@sd-jwt/jwt-status-list'
3
+ import { deflate, inflate } from 'pako'
4
+ import pkg from '@sphereon/kmp-mdoc-core';
5
+ const { com, kotlin } = pkg;
6
+ import base64url from 'base64url'
7
+ import type { IRequiredContext, SignedStatusListData } from '../../types'
8
+ import { type DecodedStatusListPayload, resolveIdentifier } from './common'
9
+
10
+ export type IKey = pkg.com.sphereon.crypto.IKey
11
+ export type CborItem<T> = pkg.com.sphereon.cbor.CborItem<T>
12
+ export const CborByteString = com.sphereon.cbor.CborByteString
13
+ export type CborByteStringType = pkg.com.sphereon.cbor.CborByteString
14
+ export const CborUInt = com.sphereon.cbor.CborUInt
15
+ export type CborUIntType = pkg.com.sphereon.cbor.CborUInt
16
+ export const CborString = com.sphereon.cbor.CborString
17
+ export type CborStringType = pkg.com.sphereon.cbor.CborString
18
+
19
+ // const cbor = cborpkg.com.sphereon.cbor
20
+ // const kmp = cborpkg.com.sphereon.kmp
21
+ // const kotlin = cborpkg.kotlin
22
+ const decompressRawStatusList = (StatusList as any).decodeStatusList.bind(StatusList)
23
+
24
+ const CWT_CLAIMS = {
25
+ SUBJECT: 2,
26
+ ISSUER: 1,
27
+ ISSUED_AT: 6,
28
+ EXPIRATION: 4,
29
+ TIME_TO_LIVE: 65534,
30
+ STATUS_LIST: 65533,
31
+ } as const
32
+
33
+ export const createSignedCbor = async (
34
+ context: IRequiredContext,
35
+ statusList: StatusList,
36
+ issuerString: string,
37
+ id: string,
38
+ expiresAt?: Date,
39
+ keyRef?: string,
40
+ ): Promise<SignedStatusListData> => {
41
+ const identifier = await resolveIdentifier(context, issuerString, keyRef)
42
+
43
+ const encodeStatusList = statusList.encodeStatusList()
44
+ const compressedList = deflate(encodeStatusList, { level: 9 })
45
+ const compressedBytes = new Int8Array(compressedList)
46
+
47
+ const statusListMap = new com.sphereon.cbor.CborMap(
48
+ kotlin.collections.KtMutableMap.fromJsMap(
49
+ new Map<CborStringType, CborItem<any>>([
50
+ [
51
+ new com.sphereon.cbor.CborString('bits'),
52
+ new com.sphereon.cbor.CborUInt(com.sphereon.kmp.LongKMP.fromNumber(statusList.getBitsPerStatus())),
53
+ ],
54
+ [new com.sphereon.cbor.CborString('lst'), new com.sphereon.cbor.CborByteString(compressedBytes)],
55
+ ]),
56
+ ),
57
+ )
58
+
59
+ const protectedHeader = new com.sphereon.cbor.CborMap(
60
+ kotlin.collections.KtMutableMap.fromJsMap(
61
+ new Map([[new com.sphereon.cbor.CborUInt(com.sphereon.kmp.LongKMP.fromNumber(16)), new com.sphereon.cbor.CborString('statuslist+cwt')]]), // "type"
62
+ ),
63
+ )
64
+ const protectedHeaderEncoded = com.sphereon.cbor.Cbor.encode(protectedHeader)
65
+ const claimsMap = buildClaimsMap(id, issuerString, statusListMap, expiresAt)
66
+ const claimsEncoded: Int8Array = com.sphereon.cbor.Cbor.encode(claimsMap)
67
+
68
+ const signedCWT: string = await context.agent.keyManagerSign({
69
+ keyRef: identifier.kmsKeyRef,
70
+ data: base64url.encode(Buffer.from(claimsEncoded)), // TODO test on RN
71
+ encoding: undefined,
72
+ })
73
+
74
+ const protectedHeaderEncodedInt8 = new Int8Array(protectedHeaderEncoded)
75
+ const claimsEncodedInt8 = new Int8Array(claimsEncoded)
76
+ const signatureBytes = base64url.decode(signedCWT)
77
+ const signatureInt8 = new Int8Array(Buffer.from(signatureBytes))
78
+
79
+ const cwtArrayElements: Array<CborItem<any>> = [
80
+ new CborByteString(protectedHeaderEncodedInt8),
81
+ new CborByteString(claimsEncodedInt8),
82
+ new CborByteString(signatureInt8),
83
+ ]
84
+ const cwtArray = new com.sphereon.cbor.CborArray(kotlin.collections.KtMutableList.fromJsArray(cwtArrayElements))
85
+ const cwtEncoded = com.sphereon.cbor.Cbor.encode(cwtArray)
86
+ const cwtBuffer = Buffer.from(cwtEncoded)
87
+ return {
88
+ statusListCredential: base64url.encode(cwtBuffer),
89
+ encodedList: base64url.encode(compressedList as Buffer), // JS in @sd-jwt/jwt-status-list drops it in like this, so keep the same method
90
+ }
91
+ }
92
+
93
+ function buildClaimsMap(id: string, issuerString: string, statusListMap: pkg.com.sphereon.cbor.CborMap<CborStringType, CborItem<any>>, expiresAt?: Date) {
94
+ const ttl = 65535 // FIXME figure out what value should be / come from and what the difference is with exp
95
+ const claimsEntries: Array<[CborUIntType, CborItem<any>]> = [
96
+ [new CborUInt(com.sphereon.kmp.LongKMP.fromNumber(CWT_CLAIMS.SUBJECT)), new com.sphereon.cbor.CborString(id)], // "sub"
97
+ [new CborUInt(com.sphereon.kmp.LongKMP.fromNumber(CWT_CLAIMS.ISSUER)), new com.sphereon.cbor.CborString(issuerString)], // "iss"
98
+ [
99
+ new CborUInt(com.sphereon.kmp.LongKMP.fromNumber(CWT_CLAIMS.ISSUED_AT)),
100
+ new CborUInt(com.sphereon.kmp.LongKMP.fromNumber(Math.floor(Date.now() / 1000))), // "iat"
101
+ ],
102
+ ]
103
+
104
+ if (expiresAt) {
105
+ claimsEntries.push([
106
+ new com.sphereon.cbor.CborUInt(com.sphereon.kmp.LongKMP.fromNumber(CWT_CLAIMS.EXPIRATION)),
107
+ new com.sphereon.cbor.CborUInt(com.sphereon.kmp.LongKMP.fromNumber(Math.floor(expiresAt.getTime() / 1000))), // "exp"
108
+ ])
109
+ }
110
+
111
+ if (ttl) {
112
+ claimsEntries.push([
113
+ new com.sphereon.cbor.CborUInt(com.sphereon.kmp.LongKMP.fromNumber(CWT_CLAIMS.TIME_TO_LIVE)),
114
+ new com.sphereon.cbor.CborUInt(com.sphereon.kmp.LongKMP.fromNumber(ttl)), // "time to live"
115
+ ])
116
+ }
117
+
118
+ claimsEntries.push([new com.sphereon.cbor.CborUInt(com.sphereon.kmp.LongKMP.fromNumber(CWT_CLAIMS.STATUS_LIST)), statusListMap])
119
+
120
+ const claimsMap = new com.sphereon.cbor.CborMap(kotlin.collections.KtMutableMap.fromJsMap(new Map(claimsEntries)))
121
+ return claimsMap
122
+ }
123
+
124
+ const getCborValueFromMap = <T>(map: Map<CborItem<any>, CborItem<any>>, key: number): T => {
125
+ const value = getCborOptionalValueFromMap<T>(map, key)
126
+ if (value === undefined) {
127
+ throw new Error(`Required claim ${key} not found`)
128
+ }
129
+ return value
130
+ }
131
+
132
+ const getCborOptionalValueFromMap = <T>(map: Map<CborItem<any>, CborItem<any>>, key: number): T | undefined | never => {
133
+ const value = map.get(new CborUInt(com.sphereon.kmp.LongKMP.fromNumber(key)))
134
+ if (!value) {
135
+ return undefined
136
+ }
137
+ return value.value as T
138
+ }
139
+
140
+ export const decodeStatusListCWT = (cwt: string): DecodedStatusListPayload => {
141
+ const encodedCbor = base64url.toBuffer(cwt)
142
+ const encodedCborArray = new Int8Array(encodedCbor)
143
+ const decodedCbor = com.sphereon.cbor.Cbor.decode(encodedCborArray)
144
+
145
+ if (!(decodedCbor instanceof com.sphereon.cbor.CborArray)) {
146
+ throw new Error('Invalid CWT format: Expected a CBOR array')
147
+ }
148
+
149
+ const [, payload] = decodedCbor.value.asJsArrayView()
150
+ if (!(payload instanceof com.sphereon.cbor.CborByteString)) {
151
+ throw new Error('Invalid payload format: Expected a CBOR ByteString')
152
+ }
153
+
154
+ const claims = com.sphereon.cbor.Cbor.decode(payload.value)
155
+ if (!(claims instanceof com.sphereon.cbor.CborMap)) {
156
+ throw new Error('Invalid claims format: Expected a CBOR map')
157
+ }
158
+
159
+ const claimsMap = claims.value.asJsMapView()
160
+
161
+ const statusListMap = claimsMap.get(new CborUInt(com.sphereon.kmp.LongKMP.fromNumber(65533))).value.asJsMapView()
162
+
163
+ const bits = Number(statusListMap.get(new CborString('bits')).value) as BitsPerStatus
164
+ const decoded = new Uint8Array(statusListMap.get(new CborString('lst')).value)
165
+ const uint8Array = inflate(decoded)
166
+ const rawStatusList = decompressRawStatusList(uint8Array, bits)
167
+ const statusList = new StatusList(rawStatusList, bits)
168
+
169
+ return {
170
+ issuer: getCborValueFromMap<string>(claimsMap, CWT_CLAIMS.ISSUER),
171
+ id: getCborValueFromMap<string>(claimsMap, CWT_CLAIMS.SUBJECT),
172
+ statusList,
173
+ iat: Number(getCborValueFromMap<number>(claimsMap, CWT_CLAIMS.ISSUED_AT)),
174
+ exp: getCborOptionalValueFromMap<number>(claimsMap, CWT_CLAIMS.EXPIRATION),
175
+ ttl: getCborOptionalValueFromMap<number>(claimsMap, CWT_CLAIMS.TIME_TO_LIVE),
176
+ }
177
+ }
@@ -0,0 +1,20 @@
1
+ import type { IRequiredContext } from '../../types'
2
+ import { StatusList } from '@sd-jwt/jwt-status-list'
3
+
4
+ export interface DecodedStatusListPayload {
5
+ issuer: string
6
+ id: string
7
+ statusList: StatusList
8
+ exp?: number
9
+ ttl?: number
10
+ iat: number
11
+ }
12
+
13
+ export const resolveIdentifier = async (context: IRequiredContext, issuer: string, keyRef?: string) => {
14
+ return await context.agent.identifierManagedGet({
15
+ identifier: issuer,
16
+ vmRelationship: 'assertionMethod',
17
+ offlineWhenNoDIDRegistered: true,
18
+ ...(keyRef && { kmsKeyRef: keyRef }), // TODO the getDid resolver should look at this ASAP
19
+ })
20
+ }
@@ -0,0 +1,80 @@
1
+ import { type CompactJWT, JoseSignatureAlgorithm } from '@sphereon/ssi-types'
2
+ import { createHeaderAndPayload, StatusList, type StatusListJWTHeaderParameters, type StatusListJWTPayload } from '@sd-jwt/jwt-status-list'
3
+ import base64url from 'base64url'
4
+ import type { JWTPayload } from 'did-jwt'
5
+ import type { IRequiredContext, SignedStatusListData } from '../../types'
6
+ import { type DecodedStatusListPayload, resolveIdentifier } from './common'
7
+ import type { TKeyType } from '@veramo/core'
8
+ import { ensureManagedIdentifierResult } from '@sphereon/ssi-sdk-ext.identifier-resolution'
9
+
10
+ const STATUS_LIST_JWT_TYP = 'statuslist+jwt'
11
+
12
+ export const createSignedJwt = async (
13
+ context: IRequiredContext,
14
+ statusList: StatusList,
15
+ issuerString: string,
16
+ id: string,
17
+ expiresAt?: Date,
18
+ keyRef?: string,
19
+ ): Promise<SignedStatusListData> => {
20
+ const identifier = await resolveIdentifier(context, issuerString, keyRef)
21
+ const resolution = await ensureManagedIdentifierResult(identifier, context)
22
+
23
+ const payload: JWTPayload = {
24
+ iss: issuerString,
25
+ sub: id,
26
+ iat: Math.floor(Date.now() / 1000),
27
+ ...(expiresAt && { exp: Math.floor(expiresAt.getTime() / 1000) }),
28
+ }
29
+
30
+ const header: StatusListJWTHeaderParameters = {
31
+ alg: getSigningAlgo(resolution.key.type),
32
+ typ: STATUS_LIST_JWT_TYP,
33
+ }
34
+ const values = createHeaderAndPayload(statusList, payload, header)
35
+ const signedJwt = await context.agent.jwtCreateJwsCompactSignature({
36
+ issuer: { ...identifier, noIssPayloadUpdate: false },
37
+ protectedHeader: values.header,
38
+ payload: values.payload,
39
+ })
40
+
41
+ return {
42
+ statusListCredential: signedJwt.jwt,
43
+ encodedList: (values.payload as StatusListJWTPayload).status_list.lst,
44
+ }
45
+ }
46
+
47
+ export const decodeStatusListJWT = (jwt: CompactJWT): DecodedStatusListPayload => {
48
+ const [, payloadBase64] = jwt.split('.')
49
+ const payload = JSON.parse(base64url.decode(payloadBase64))
50
+
51
+ if (!payload.iss || !payload.sub || !payload.status_list) {
52
+ throw new Error('Missing required fields in JWT payload')
53
+ }
54
+
55
+ const statusList = StatusList.decompressStatusList(payload.status_list.lst, payload.status_list.bits)
56
+
57
+ return {
58
+ issuer: payload.iss,
59
+ id: payload.sub,
60
+ statusList,
61
+ exp: payload.exp,
62
+ ttl: payload.ttl,
63
+ iat: payload.iat,
64
+ }
65
+ }
66
+
67
+ export const getSigningAlgo = (type: TKeyType): JoseSignatureAlgorithm => {
68
+ switch (type) {
69
+ case 'Ed25519':
70
+ return JoseSignatureAlgorithm.EdDSA
71
+ case 'Secp256k1':
72
+ return JoseSignatureAlgorithm.ES256K
73
+ case 'Secp256r1':
74
+ return JoseSignatureAlgorithm.ES256
75
+ case 'RSA':
76
+ return JoseSignatureAlgorithm.RS256
77
+ default:
78
+ throw Error('Key type not yet supported')
79
+ }
80
+ }
@@ -1,75 +1,123 @@
1
- import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
1
+ import type { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
2
2
  import {
3
- ICredential,
4
- ICredentialStatus,
5
- IIssuer,
6
- IVerifiableCredential,
7
- OriginalVerifiableCredential,
8
- OrPromise,
3
+ type ICredential,
4
+ type ICredentialStatus,
5
+ type IIssuer,
6
+ type IVerifiableCredential,
7
+ type OrPromise,
8
+ type CredentialProofFormat,
9
+ type StatusListCredential,
9
10
  StatusListCredentialIdMode,
10
11
  StatusListDriverType,
11
- StatusListIndexingDirection,
12
+ type StatusListIndexingDirection,
12
13
  StatusListType,
13
- StatusPurpose2021,
14
+ type StatusPurpose2021,
14
15
  } from '@sphereon/ssi-types'
15
- import {
16
+ import type {
16
17
  CredentialPayload,
17
18
  IAgentContext,
18
19
  ICredentialIssuer,
19
20
  ICredentialPlugin,
20
21
  ICredentialVerifier,
22
+ IKeyManager,
21
23
  IPluginMethodMap,
22
- ProofFormat,
23
24
  } from '@veramo/core'
24
25
  import { DataSource } from 'typeorm'
26
+ import type { BitsPerStatus } from '@sd-jwt/jwt-status-list'
27
+ import type { SdJwtVcPayload } from '@sd-jwt/sd-jwt-vc'
28
+ import type { StatusListOpts } from '@sphereon/oid4vci-common'
25
29
 
26
- export interface CreateNewStatusListFuncArgs extends Omit<StatusList2021ToVerifiableCredentialArgs, 'encodedList'> {
27
- correlationId: string
28
- length?: number
30
+ export enum StatusOAuth {
31
+ Valid = 0,
32
+ Invalid = 1,
33
+ Suspended = 2,
29
34
  }
30
35
 
31
- export interface UpdateStatusListFromEncodedListArgs extends StatusList2021ToVerifiableCredentialArgs {
32
- statusListIndex: number | string
33
- value: boolean
36
+ export enum Status2021 {
37
+ Valid = 0,
38
+ Invalid = 1,
34
39
  }
35
40
 
36
- export interface UpdateStatusListFromStatusListCredentialArgs {
37
- statusListCredential: OriginalVerifiableCredential
38
- keyRef?: string
39
- statusListIndex: number | string
40
- value: boolean
41
+ export type StatusList2021Args = {
42
+ indexingDirection: StatusListIndexingDirection
43
+ statusPurpose?: StatusPurpose2021
44
+ // todo: validFrom and validUntil
41
45
  }
42
46
 
43
- export interface StatusList2021ToVerifiableCredentialArgs {
44
- issuer: string | IIssuer
47
+ export type OAuthStatusListArgs = {
48
+ bitsPerStatus?: BitsPerStatus
49
+ expiresAt?: Date
50
+ }
51
+
52
+ export type BaseCreateNewStatusListArgs = {
53
+ type: StatusListType
45
54
  id: string
46
- type?: StatusListType
55
+ issuer: string | IIssuer
56
+ correlationId?: string
57
+ length?: number
58
+ proofFormat?: CredentialProofFormat
59
+ keyRef?: string
60
+ statusList2021?: StatusList2021Args
61
+ oauthStatusList?: OAuthStatusListArgs
62
+ driverType?: StatusListDriverType
63
+ }
64
+
65
+ export type UpdateStatusList2021Args = {
47
66
  statusPurpose: StatusPurpose2021
48
- encodedList: string
49
- proofFormat?: ProofFormat
67
+ }
68
+
69
+ export type UpdateOAuthStatusListArgs = {
70
+ bitsPerStatus: BitsPerStatus
71
+ expiresAt?: Date
72
+ }
73
+
74
+ export interface UpdateStatusListFromEncodedListArgs {
75
+ type?: StatusListType
76
+ statusListIndex: number | string
77
+ value: boolean
78
+ proofFormat?: CredentialProofFormat
50
79
  keyRef?: string
80
+ correlationId?: string
81
+ encodedList: string
82
+ issuer: string | IIssuer
83
+ id: string
84
+ statusList2021?: UpdateStatusList2021Args
85
+ oauthStatusList?: UpdateOAuthStatusListArgs
86
+ }
51
87
 
52
- // todo: validFrom and validUntil
88
+ export interface UpdateStatusListFromStatusListCredentialArgs {
89
+ statusListCredential: StatusListCredential // | CompactJWT
90
+ keyRef?: string
91
+ statusListIndex: number | string
92
+ value: number | Status2021 | StatusOAuth
53
93
  }
54
94
 
55
- export interface StatusListDetails {
95
+ export interface StatusListResult {
56
96
  encodedList: string
97
+ statusListCredential: StatusListCredential
57
98
  length: number
58
99
  type: StatusListType
59
- proofFormat: ProofFormat
60
- statusPurpose: StatusPurpose2021
100
+ proofFormat: CredentialProofFormat
61
101
  id: string
102
+ statuslistContentType: string
62
103
  issuer: string | IIssuer
63
- indexingDirection: StatusListIndexingDirection
64
- statusListCredential: OriginalVerifiableCredential
104
+ statusList2021?: StatusList2021Details
105
+ oauthStatusList?: OAuthStatusDetails
106
+
65
107
  // These cannot be deduced from the VC, so they are present when callers pass in these values as params
66
108
  correlationId?: string
67
109
  driverType?: StatusListDriverType
68
110
  credentialIdMode?: StatusListCredentialIdMode
69
111
  }
70
112
 
71
- export interface StatusListResult extends StatusListDetails {
72
- statusListCredential: OriginalVerifiableCredential
113
+ interface StatusList2021Details {
114
+ indexingDirection: StatusListIndexingDirection
115
+ statusPurpose?: StatusPurpose2021
116
+ }
117
+
118
+ interface OAuthStatusDetails {
119
+ bitsPerStatus?: BitsPerStatus
120
+ expiresAt?: Date
73
121
  }
74
122
 
75
123
  export interface StatusList2021EntryCredentialStatus extends ICredentialStatus {
@@ -79,6 +127,54 @@ export interface StatusList2021EntryCredentialStatus extends ICredentialStatus {
79
127
  statusListCredential: string
80
128
  }
81
129
 
130
+ export interface StatusListOAuthEntryCredentialStatus extends ICredentialStatus {
131
+ type: 'OAuthStatusListEntry'
132
+ bitsPerStatus: number
133
+ statusListIndex: string
134
+ statusListCredential: string
135
+ expiresAt?: Date
136
+ }
137
+
138
+ export interface StatusList2021ToVerifiableCredentialArgs {
139
+ issuer: string | IIssuer
140
+ id: string
141
+ type?: StatusListType
142
+ proofFormat?: CredentialProofFormat
143
+ keyRef?: string
144
+ encodedList: string
145
+ statusPurpose: StatusPurpose2021
146
+ }
147
+
148
+ export interface CreateStatusListArgs {
149
+ issuer: string | IIssuer
150
+ id: string
151
+ proofFormat?: CredentialProofFormat
152
+ keyRef?: string
153
+ correlationId?: string
154
+ length?: number
155
+ statusList2021?: StatusList2021Args
156
+ oauthStatusList?: OAuthStatusListArgs
157
+ }
158
+
159
+ export interface UpdateStatusListIndexArgs {
160
+ statusListCredential: StatusListCredential // | CompactJWT
161
+ statusListIndex: number | string
162
+ value: number | Status2021 | StatusOAuth
163
+ keyRef?: string
164
+ expiresAt?: Date
165
+ }
166
+
167
+ export interface CheckStatusIndexArgs {
168
+ statusListCredential: StatusListCredential // | CompactJWT
169
+ statusListIndex: string | number
170
+ }
171
+
172
+ export interface ToStatusListDetailsArgs {
173
+ statusListPayload: StatusListCredential
174
+ correlationId?: string
175
+ driverType?: StatusListDriverType
176
+ }
177
+
82
178
  /**
83
179
  * The interface definition for a plugin that can add statuslist info to a credential
84
180
  *
@@ -95,7 +191,7 @@ export interface IStatusListPlugin extends IPluginMethodMap {
95
191
  *
96
192
  * @returns - The details of the newly created status list
97
193
  */
98
- slCreateStatusList(args: CreateNewStatusListArgs, context: IRequiredContext): Promise<StatusListDetails>
194
+ slCreateStatusList(args: CreateNewStatusListArgs, context: IRequiredContext): Promise<StatusListResult>
99
195
 
100
196
  /**
101
197
  * Ensures status list info like index and list id is added to a credential
@@ -109,25 +205,44 @@ export interface IStatusListPlugin extends IPluginMethodMap {
109
205
  */
110
206
  slAddStatusToCredential(args: IAddStatusToCredentialArgs, context: IRequiredContext): Promise<CredentialWithStatusSupport>
111
207
 
208
+ slAddStatusToSdJwtCredential(args: IAddStatusToSdJwtCredentialArgs, context: IRequiredContext): Promise<SdJwtVcPayload>
209
+
112
210
  /**
113
211
  * Get the status list using the configured driver for the SL. Normally a correlationId or id should suffice. Optionally accepts a dbName/datasource
114
212
  * @param args
115
213
  * @param context
116
214
  */
117
- slGetStatusList(args: GetStatusListArgs, context: IRequiredContext): Promise<StatusListDetails>
215
+ slGetStatusList(args: GetStatusListArgs, context: IRequiredContext): Promise<StatusListResult>
216
+
217
+ /**
218
+ * Import status lists when noy yet present
219
+ *
220
+ * @param imports Array of status list information like type and size
221
+ * @param context - This reserved param is automatically added and handled by the framework, *do not override*
222
+ */
223
+ slImportStatusLists(imports: Array<CreateNewStatusListArgs>, context: IRequiredContext): Promise<boolean>
224
+ }
225
+
226
+ export type CreateNewStatusListFuncArgs = BaseCreateNewStatusListArgs
227
+
228
+ export type CreateNewStatusListArgs = BaseCreateNewStatusListArgs & {
229
+ dbName?: string
230
+ dataSource?: OrPromise<DataSource>
231
+ isDefault?: boolean
118
232
  }
119
233
 
120
234
  export type IAddStatusToCredentialArgs = Omit<IIssueCredentialStatusOpts, 'dataSource'> & {
121
235
  credential: CredentialWithStatusSupport
122
236
  }
123
237
 
238
+ export type IAddStatusToSdJwtCredentialArgs = Omit<IIssueCredentialStatusOpts, 'dataSource'> & {
239
+ credential: SdJwtVcPayload
240
+ }
241
+
124
242
  export interface IIssueCredentialStatusOpts {
125
243
  dataSource?: DataSource
126
-
244
+ statusLists?: Array<StatusListOpts>
127
245
  credentialId?: string // An id to use for the credential. Normally should be set as the crdential.id value
128
- statusListId?: string // Explicit status list to use. Determines the id from the credentialStatus object in the VC itself or uses the default otherwise
129
- statusListIndex?: number | string
130
- statusEntryCorrelationId?: string // An id to use for correlation. Can be the credential id, but also a business identifier. Will only be used for lookups/management
131
246
  value?: string
132
247
  }
133
248
 
@@ -138,13 +253,12 @@ export type GetStatusListArgs = {
138
253
  dbName?: string
139
254
  }
140
255
 
141
- export type CreateNewStatusListArgs = CreateNewStatusListFuncArgs & {
142
- dataSource?: OrPromise<DataSource>
143
- dbName?: string
144
- isDefault?: boolean
145
- }
146
-
147
256
  export type CredentialWithStatusSupport = ICredential | CredentialPayload | IVerifiableCredential
148
257
 
258
+ export type SignedStatusListData = {
259
+ statusListCredential: StatusListCredential
260
+ encodedList: string
261
+ }
262
+
149
263
  export type IRequiredPlugins = ICredentialPlugin & IIdentifierResolution
150
- export type IRequiredContext = IAgentContext<ICredentialIssuer & ICredentialVerifier & IIdentifierResolution>
264
+ export type IRequiredContext = IAgentContext<ICredentialIssuer & ICredentialVerifier & IIdentifierResolution & IKeyManager & ICredentialPlugin>
package/src/utils.ts ADDED
@@ -0,0 +1,95 @@
1
+ import {
2
+ CredentialMapper,
3
+ type IIssuer,
4
+ type CredentialProofFormat,
5
+ StatusListType,
6
+ StatusListType as StatusListTypeW3C,
7
+ type StatusListCredential,
8
+ DocumentFormat,
9
+ } from '@sphereon/ssi-types'
10
+ import { jwtDecode } from 'jwt-decode'
11
+
12
+ export function getAssertedStatusListType(type?: StatusListType) {
13
+ const assertedType = type ?? StatusListType.StatusList2021
14
+ if (![StatusListType.StatusList2021, StatusListType.OAuthStatusList].includes(assertedType)) {
15
+ throw Error(`StatusList type ${assertedType} is not supported (yet)`)
16
+ }
17
+ return assertedType
18
+ }
19
+
20
+ export function getAssertedValue<T>(name: string, value: T): NonNullable<T> {
21
+ if (value === undefined || value === null) {
22
+ throw Error(`Missing required ${name} value`)
23
+ }
24
+ return value
25
+ }
26
+
27
+ export function getAssertedValues(args: { issuer: string | IIssuer; id: string; type?: StatusListTypeW3C | StatusListType }) {
28
+ const type = getAssertedStatusListType(args?.type)
29
+ const id = getAssertedValue('id', args.id)
30
+ const issuer = getAssertedValue('issuer', args.issuer)
31
+ return { id, issuer, type }
32
+ }
33
+
34
+ export function getAssertedProperty<T extends object>(propertyName: string, obj: T): NonNullable<any> {
35
+ if (!(propertyName in obj)) {
36
+ throw Error(`The input object does not contain required property: ${propertyName}`)
37
+ }
38
+ return getAssertedValue(propertyName, (obj as any)[propertyName])
39
+ }
40
+
41
+ const ValidProofTypeMap = new Map<StatusListType, CredentialProofFormat[]>([
42
+ [StatusListType.StatusList2021, ['jwt', 'lds', 'EthereumEip712Signature2021']],
43
+ [StatusListType.OAuthStatusList, ['jwt', 'cbor']],
44
+ ])
45
+
46
+ export function assertValidProofType(type: StatusListType, proofFormat: CredentialProofFormat) {
47
+ const validProofTypes = ValidProofTypeMap.get(type)
48
+ if (!validProofTypes?.includes(proofFormat)) {
49
+ throw Error(`Invalid proof format '${proofFormat}' for status list type ${type}`)
50
+ }
51
+ }
52
+
53
+ export function determineStatusListType(credential: StatusListCredential): StatusListType {
54
+ const proofFormat = determineProofFormat(credential)
55
+ switch (proofFormat) {
56
+ case 'jwt':
57
+ const payload: StatusListCredential = jwtDecode(credential as string)
58
+ const keys = Object.keys(payload)
59
+ if (keys.includes('status_list')) {
60
+ return StatusListType.OAuthStatusList
61
+ } else if (keys.includes('vc')) {
62
+ return StatusListType.StatusList2021
63
+ }
64
+ break
65
+ case 'lds':
66
+ const uniform = CredentialMapper.toUniformCredential(credential)
67
+ const type = uniform.type.find((t) => {
68
+ return Object.values(StatusListType).some((statusType) => t.includes(statusType))
69
+ })
70
+ if (!type) {
71
+ throw new Error('Invalid status list credential type')
72
+ }
73
+ return type.replace('Credential', '') as StatusListType
74
+
75
+ case 'cbor':
76
+ return StatusListType.OAuthStatusList
77
+ }
78
+
79
+ throw new Error('Cannot determine status list type from credential payload')
80
+ }
81
+
82
+ export function determineProofFormat(credential: StatusListCredential): CredentialProofFormat {
83
+ const type: DocumentFormat = CredentialMapper.detectDocumentType(credential)
84
+ switch (type) {
85
+ case DocumentFormat.JWT:
86
+ return 'jwt'
87
+ case DocumentFormat.MSO_MDOC:
88
+ // Not really mdoc, just assume Cbor for now, I'd need to decode at least the header to what type of Cbor we have
89
+ return 'cbor'
90
+ case DocumentFormat.JSONLD:
91
+ return 'lds'
92
+ default:
93
+ throw Error('Cannot determine credential payload type')
94
+ }
95
+ }