@sphereon/ssi-types 0.30.1-unstable.4 → 0.30.1
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/LICENSE +201 -201
- package/README.md +49 -49
- package/dist/events/index.d.ts +1 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/logging/index.d.ts +1 -0
- package/dist/logging/index.d.ts.map +1 -1
- package/dist/logging/index.js +2 -2
- package/dist/logging/index.js.map +1 -1
- package/dist/mapper/jsonld-language-values.d.ts.map +1 -1
- package/dist/types/mso_mdoc.js +5 -5
- package/dist/types/mso_mdoc.js.map +1 -1
- package/dist/types/sd-jwt-vc.js +5 -5
- package/dist/types/sd-jwt-vc.js.map +1 -1
- package/dist/types/w3c-vc.js +3 -3
- package/dist/types/w3c-vc.js.map +1 -1
- package/package.json +4 -4
- package/src/events/index.ts +206 -206
- package/src/index.ts +9 -9
- package/src/logging/index.ts +224 -224
- package/src/mapper/credential-mapper.ts +919 -919
- package/src/mapper/index.ts +2 -2
- package/src/mapper/jsonld-language-values.ts +91 -91
- package/src/types/cose.ts +69 -69
- package/src/types/did.ts +268 -268
- package/src/types/generic.ts +48 -48
- package/src/types/index.ts +9 -9
- package/src/types/jose.ts +115 -115
- package/src/types/mso_mdoc.ts +174 -174
- package/src/types/pex.ts +36 -36
- package/src/types/sd-jwt-vc.ts +369 -369
- package/src/types/vc.ts +87 -87
- package/src/types/w3c-vc.ts +306 -306
- package/src/utils/index.ts +1 -1
- package/src/utils/object.ts +28 -28
|
@@ -1,919 +1,919 @@
|
|
|
1
|
-
import { IssuerType } from '@veramo/core'
|
|
2
|
-
import jwt_decode from 'jwt-decode'
|
|
3
|
-
import {
|
|
4
|
-
AsyncHasher,
|
|
5
|
-
decodeMdocDeviceResponse,
|
|
6
|
-
decodeMdocIssuerSigned,
|
|
7
|
-
decodeSdJwtVc,
|
|
8
|
-
decodeSdJwtVcAsync,
|
|
9
|
-
DocumentFormat,
|
|
10
|
-
Hasher,
|
|
11
|
-
ICredential,
|
|
12
|
-
IPresentation,
|
|
13
|
-
IProof,
|
|
14
|
-
IProofPurpose,
|
|
15
|
-
IProofType,
|
|
16
|
-
isWrappedMdocCredential,
|
|
17
|
-
isWrappedMdocPresentation,
|
|
18
|
-
isWrappedSdJwtVerifiableCredential,
|
|
19
|
-
isWrappedSdJwtVerifiablePresentation,
|
|
20
|
-
isWrappedW3CVerifiableCredential,
|
|
21
|
-
isWrappedW3CVerifiablePresentation,
|
|
22
|
-
IVerifiableCredential,
|
|
23
|
-
IVerifiablePresentation,
|
|
24
|
-
JwtDecodedVerifiableCredential,
|
|
25
|
-
JwtDecodedVerifiablePresentation,
|
|
26
|
-
mdocDecodedCredentialToUniformCredential,
|
|
27
|
-
MdocDeviceResponse,
|
|
28
|
-
MdocOid4vpMdocVpToken,
|
|
29
|
-
OriginalType,
|
|
30
|
-
OriginalVerifiableCredential,
|
|
31
|
-
OriginalVerifiablePresentation,
|
|
32
|
-
sdJwtDecodedCredentialToUniformCredential,
|
|
33
|
-
SdJwtDecodedVerifiableCredential,
|
|
34
|
-
SdJwtDecodedVerifiableCredentialPayload,
|
|
35
|
-
UniformVerifiablePresentation,
|
|
36
|
-
W3CVerifiableCredential,
|
|
37
|
-
W3CVerifiablePresentation,
|
|
38
|
-
WrappedMdocCredential,
|
|
39
|
-
WrappedSdJwtVerifiableCredential,
|
|
40
|
-
WrappedVerifiableCredential,
|
|
41
|
-
WrappedVerifiablePresentation,
|
|
42
|
-
WrappedW3CVerifiableCredential,
|
|
43
|
-
} from '../types'
|
|
44
|
-
import { MdocDocument } from '../types/mso_mdoc'
|
|
45
|
-
import { ObjectUtils } from '../utils'
|
|
46
|
-
import { com } from '@sphereon/kmp-mdl-mdoc'
|
|
47
|
-
import DeviceResponseCbor = com.sphereon.mdoc.data.device.DeviceResponseCbor
|
|
48
|
-
|
|
49
|
-
export class CredentialMapper {
|
|
50
|
-
/**
|
|
51
|
-
* Decodes a compact SD-JWT vc to it's decoded variant. This method can be used when the hasher implementation used is Async, and therefore not suitable for usage
|
|
52
|
-
* with the other decode methods.
|
|
53
|
-
*/
|
|
54
|
-
static decodeSdJwtVcAsync(compactSdJwtVc: string, hasher: AsyncHasher) {
|
|
55
|
-
return decodeSdJwtVcAsync(compactSdJwtVc, hasher)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Decodes a Verifiable Presentation to a uniform format.
|
|
60
|
-
*
|
|
61
|
-
* When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
|
|
62
|
-
* an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
|
|
63
|
-
* instead of the compact SD-JWT.
|
|
64
|
-
*
|
|
65
|
-
* @param hasher Hasher implementation to use for SD-JWT decoding.
|
|
66
|
-
*/
|
|
67
|
-
static decodeVerifiablePresentation(
|
|
68
|
-
presentation: OriginalVerifiablePresentation,
|
|
69
|
-
hasher?: Hasher
|
|
70
|
-
): JwtDecodedVerifiablePresentation | IVerifiablePresentation | SdJwtDecodedVerifiableCredential | MdocOid4vpMdocVpToken | MdocDeviceResponse {
|
|
71
|
-
if (CredentialMapper.isJwtEncoded(presentation)) {
|
|
72
|
-
const payload = jwt_decode(presentation as string) as JwtDecodedVerifiablePresentation
|
|
73
|
-
const header = jwt_decode(presentation as string, { header: true }) as Record<string, any>
|
|
74
|
-
|
|
75
|
-
payload.vp.proof = {
|
|
76
|
-
type: IProofType.JwtProof2020,
|
|
77
|
-
created: payload.nbf,
|
|
78
|
-
proofPurpose: IProofPurpose.authentication,
|
|
79
|
-
verificationMethod: header['kid'] ?? payload.iss,
|
|
80
|
-
jwt: presentation as string,
|
|
81
|
-
}
|
|
82
|
-
return payload
|
|
83
|
-
} else if (CredentialMapper.isJwtDecodedPresentation(presentation)) {
|
|
84
|
-
return presentation as JwtDecodedVerifiablePresentation
|
|
85
|
-
} else if (CredentialMapper.isSdJwtEncoded(presentation)) {
|
|
86
|
-
if (!hasher) {
|
|
87
|
-
throw new Error('Hasher implementation is required to decode SD-JWT')
|
|
88
|
-
}
|
|
89
|
-
return decodeSdJwtVc(presentation, hasher)
|
|
90
|
-
} else if (CredentialMapper.isSdJwtDecodedCredential(presentation)) {
|
|
91
|
-
return presentation as SdJwtDecodedVerifiableCredential
|
|
92
|
-
} else if (CredentialMapper.isMsoMdocOid4VPEncoded(presentation)) {
|
|
93
|
-
return presentation as MdocOid4vpMdocVpToken
|
|
94
|
-
} else if (CredentialMapper.isMsoMdocDecodedPresentation(presentation)) {
|
|
95
|
-
return presentation as MdocDeviceResponse
|
|
96
|
-
} else if (CredentialMapper.isJsonLdAsString(presentation)) {
|
|
97
|
-
return JSON.parse(presentation as string) as IVerifiablePresentation
|
|
98
|
-
} else {
|
|
99
|
-
return presentation as IVerifiablePresentation
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Decodes a Verifiable Credential to a uniform format.
|
|
105
|
-
*
|
|
106
|
-
* When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
|
|
107
|
-
* an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
|
|
108
|
-
* instead of the compact SD-JWT.
|
|
109
|
-
*
|
|
110
|
-
* @param hasher Hasher implementation to use for SD-JWT decoding
|
|
111
|
-
*/
|
|
112
|
-
static decodeVerifiableCredential(
|
|
113
|
-
credential: OriginalVerifiableCredential,
|
|
114
|
-
hasher?: Hasher
|
|
115
|
-
): JwtDecodedVerifiableCredential | IVerifiableCredential | SdJwtDecodedVerifiableCredential {
|
|
116
|
-
if (CredentialMapper.isJwtEncoded(credential)) {
|
|
117
|
-
const payload = jwt_decode(credential as string) as JwtDecodedVerifiableCredential
|
|
118
|
-
const header = jwt_decode(credential as string, { header: true }) as Record<string, any>
|
|
119
|
-
payload.vc.proof = {
|
|
120
|
-
type: IProofType.JwtProof2020,
|
|
121
|
-
created: payload.nbf,
|
|
122
|
-
proofPurpose: IProofPurpose.authentication,
|
|
123
|
-
verificationMethod: header['kid'] ?? payload.iss,
|
|
124
|
-
jwt: credential as string,
|
|
125
|
-
}
|
|
126
|
-
return payload
|
|
127
|
-
} else if (CredentialMapper.isJwtDecodedCredential(credential)) {
|
|
128
|
-
return credential
|
|
129
|
-
} else if (CredentialMapper.isJsonLdAsString(credential)) {
|
|
130
|
-
return JSON.parse(credential as string) as IVerifiableCredential
|
|
131
|
-
} else if (CredentialMapper.isSdJwtEncoded(credential)) {
|
|
132
|
-
if (!hasher) {
|
|
133
|
-
throw new Error('Hasher implementation is required to decode SD-JWT')
|
|
134
|
-
}
|
|
135
|
-
return decodeSdJwtVc(credential, hasher)
|
|
136
|
-
} else if (CredentialMapper.isSdJwtDecodedCredential(credential)) {
|
|
137
|
-
return credential
|
|
138
|
-
} else {
|
|
139
|
-
return credential as IVerifiableCredential
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Converts a presentation to a wrapped presentation.
|
|
145
|
-
*
|
|
146
|
-
* When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
|
|
147
|
-
* an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
|
|
148
|
-
* instead of the compact SD-JWT.
|
|
149
|
-
*
|
|
150
|
-
* @param hasher Hasher implementation to use for SD-JWT decoding
|
|
151
|
-
*/
|
|
152
|
-
static toWrappedVerifiablePresentation(
|
|
153
|
-
originalPresentation: OriginalVerifiablePresentation,
|
|
154
|
-
opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
|
|
155
|
-
): WrappedVerifiablePresentation {
|
|
156
|
-
// MSO_MDOC
|
|
157
|
-
if (CredentialMapper.isMsoMdocDecodedPresentation(originalPresentation) || CredentialMapper.isMsoMdocOid4VPEncoded(originalPresentation)) {
|
|
158
|
-
let deviceResponse: MdocDeviceResponse
|
|
159
|
-
if (CredentialMapper.isMsoMdocOid4VPEncoded(originalPresentation)) {
|
|
160
|
-
deviceResponse = decodeMdocDeviceResponse(originalPresentation)
|
|
161
|
-
} else {
|
|
162
|
-
deviceResponse = originalPresentation
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const mdocCredential: WrappedMdocCredential | undefined = deviceResponse.documents
|
|
166
|
-
?.map((doc) => CredentialMapper.toWrappedVerifiableCredential(doc, opts) as WrappedMdocCredential)
|
|
167
|
-
.find((value) => value !== undefined)
|
|
168
|
-
if (mdocCredential === undefined) {
|
|
169
|
-
throw Error('mdoc credential data corruption')
|
|
170
|
-
}
|
|
171
|
-
return {
|
|
172
|
-
type: CredentialMapper.isMsoMdocDecodedPresentation(originalPresentation) ? OriginalType.MSO_MDOC_DECODED : OriginalType.MSO_MDOC_ENCODED,
|
|
173
|
-
format: 'mso_mdoc',
|
|
174
|
-
original: originalPresentation,
|
|
175
|
-
presentation: deviceResponse,
|
|
176
|
-
decoded: deviceResponse,
|
|
177
|
-
vcs: [mdocCredential],
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
// SD-JWT
|
|
181
|
-
if (CredentialMapper.isSdJwtDecodedCredential(originalPresentation) || CredentialMapper.isSdJwtEncoded(originalPresentation)) {
|
|
182
|
-
let decodedPresentation: SdJwtDecodedVerifiableCredential
|
|
183
|
-
if (CredentialMapper.isSdJwtEncoded(originalPresentation)) {
|
|
184
|
-
if (!opts?.hasher) {
|
|
185
|
-
throw new Error('Hasher implementation is required to decode SD-JWT')
|
|
186
|
-
}
|
|
187
|
-
decodedPresentation = decodeSdJwtVc(originalPresentation, opts.hasher)
|
|
188
|
-
} else {
|
|
189
|
-
decodedPresentation = originalPresentation
|
|
190
|
-
}
|
|
191
|
-
return {
|
|
192
|
-
type: CredentialMapper.isSdJwtDecodedCredential(originalPresentation) ? OriginalType.SD_JWT_VC_DECODED : OriginalType.SD_JWT_VC_ENCODED,
|
|
193
|
-
format: 'vc+sd-jwt',
|
|
194
|
-
original: originalPresentation,
|
|
195
|
-
presentation: decodedPresentation,
|
|
196
|
-
decoded: decodedPresentation.decodedPayload,
|
|
197
|
-
// NOTE: we also include the SD-JWT VC as the VC, as the SD-JWT acts as both the VC and the VP
|
|
198
|
-
vcs: [CredentialMapper.toWrappedVerifiableCredential(originalPresentation, opts) as WrappedSdJwtVerifiableCredential],
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// If the VP is not an encoded/decoded SD-JWT, we assume it will be a W3C VC
|
|
203
|
-
const proof = CredentialMapper.getFirstProof(originalPresentation)
|
|
204
|
-
const original =
|
|
205
|
-
typeof originalPresentation !== 'string' && CredentialMapper.hasJWTProofType(originalPresentation) ? proof?.jwt : originalPresentation
|
|
206
|
-
if (!original) {
|
|
207
|
-
throw Error(
|
|
208
|
-
'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof'
|
|
209
|
-
)
|
|
210
|
-
}
|
|
211
|
-
const decoded = CredentialMapper.decodeVerifiablePresentation(original) as IVerifiablePresentation | JwtDecodedVerifiablePresentation
|
|
212
|
-
const isJwtEncoded: boolean = CredentialMapper.isJwtEncoded(original)
|
|
213
|
-
const isJwtDecoded: boolean = CredentialMapper.isJwtDecodedPresentation(original)
|
|
214
|
-
|
|
215
|
-
const type = isJwtEncoded ? OriginalType.JWT_ENCODED : isJwtDecoded ? OriginalType.JWT_DECODED : OriginalType.JSONLD
|
|
216
|
-
const format = isJwtDecoded || isJwtEncoded ? 'jwt_vp' : ('ldp_vp' as const)
|
|
217
|
-
|
|
218
|
-
let vp: OriginalVerifiablePresentation
|
|
219
|
-
if (isJwtEncoded || isJwtDecoded) {
|
|
220
|
-
vp = CredentialMapper.jwtDecodedPresentationToUniformPresentation(decoded as JwtDecodedVerifiablePresentation, false, opts)
|
|
221
|
-
} else {
|
|
222
|
-
vp = decoded as IVerifiablePresentation
|
|
223
|
-
}
|
|
224
|
-
if (!vp) {
|
|
225
|
-
throw Error(`VP key not found`)
|
|
226
|
-
}
|
|
227
|
-
const noVCs = !('verifiableCredential' in vp) || !vp.verifiableCredential || vp.verifiableCredential.length === 0
|
|
228
|
-
if (noVCs) {
|
|
229
|
-
console.warn(`Presentation without verifiable credentials. That is rare! `)
|
|
230
|
-
// throw Error(`VP needs to have at least one verifiable credential at this point`)
|
|
231
|
-
}
|
|
232
|
-
const vcs = noVCs
|
|
233
|
-
? []
|
|
234
|
-
: (CredentialMapper.toWrappedVerifiableCredentials(
|
|
235
|
-
vp.verifiableCredential ?? [] /*.map(value => value.original)*/,
|
|
236
|
-
opts
|
|
237
|
-
) as WrappedW3CVerifiableCredential[])
|
|
238
|
-
|
|
239
|
-
const presentation = {
|
|
240
|
-
...vp,
|
|
241
|
-
verifiableCredential: vcs, // We overwrite the verifiableCredentials with wrapped versions, making it an InternalVerifiablePresentation. Note: we keep the singular key name of the vc data model
|
|
242
|
-
} as UniformVerifiablePresentation
|
|
243
|
-
return {
|
|
244
|
-
type,
|
|
245
|
-
format,
|
|
246
|
-
original,
|
|
247
|
-
decoded,
|
|
248
|
-
presentation,
|
|
249
|
-
vcs,
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Converts a list of credentials to a list of wrapped credentials.
|
|
255
|
-
*
|
|
256
|
-
* When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
|
|
257
|
-
* an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
|
|
258
|
-
* instead of the compact SD-JWT.
|
|
259
|
-
*
|
|
260
|
-
* @param hasher Hasher implementation to use for SD-JWT decoding
|
|
261
|
-
*/
|
|
262
|
-
static toWrappedVerifiableCredentials(
|
|
263
|
-
verifiableCredentials: OriginalVerifiableCredential[],
|
|
264
|
-
opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
|
|
265
|
-
): WrappedVerifiableCredential[] {
|
|
266
|
-
return verifiableCredentials.map((vc) => CredentialMapper.toWrappedVerifiableCredential(vc, opts))
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Converts a credential to a wrapped credential.
|
|
271
|
-
*
|
|
272
|
-
* When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
|
|
273
|
-
* an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
|
|
274
|
-
* instead of the compact SD-JWT.
|
|
275
|
-
*
|
|
276
|
-
* @param hasher Hasher implementation to use for SD-JWT decoding
|
|
277
|
-
*/
|
|
278
|
-
static toWrappedVerifiableCredential(
|
|
279
|
-
verifiableCredential: OriginalVerifiableCredential,
|
|
280
|
-
opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
|
|
281
|
-
): WrappedVerifiableCredential {
|
|
282
|
-
// MSO_MDOC
|
|
283
|
-
if (CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential) || CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential)) {
|
|
284
|
-
let mdoc: MdocDocument
|
|
285
|
-
if (CredentialMapper.isMsoMdocOid4VPEncoded(verifiableCredential)) {
|
|
286
|
-
mdoc = decodeMdocIssuerSigned(verifiableCredential)
|
|
287
|
-
} else {
|
|
288
|
-
mdoc = verifiableCredential
|
|
289
|
-
}
|
|
290
|
-
return {
|
|
291
|
-
type: CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential) ? OriginalType.MSO_MDOC_DECODED : OriginalType.MSO_MDOC_ENCODED,
|
|
292
|
-
format: 'mso_mdoc',
|
|
293
|
-
original: verifiableCredential,
|
|
294
|
-
credential: mdocDecodedCredentialToUniformCredential(mdoc),
|
|
295
|
-
decoded: mdoc,
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// SD-JWT
|
|
300
|
-
if (CredentialMapper.isSdJwtDecodedCredential(verifiableCredential) || CredentialMapper.isSdJwtEncoded(verifiableCredential)) {
|
|
301
|
-
let decodedCredential: SdJwtDecodedVerifiableCredential
|
|
302
|
-
if (CredentialMapper.isSdJwtEncoded(verifiableCredential)) {
|
|
303
|
-
if (!opts?.hasher) {
|
|
304
|
-
throw new Error('Hasher implementation is required to decode SD-JWT')
|
|
305
|
-
}
|
|
306
|
-
decodedCredential = decodeSdJwtVc(verifiableCredential, opts.hasher)
|
|
307
|
-
} else {
|
|
308
|
-
decodedCredential = verifiableCredential
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return {
|
|
312
|
-
type: CredentialMapper.isSdJwtDecodedCredential(verifiableCredential) ? OriginalType.SD_JWT_VC_DECODED : OriginalType.SD_JWT_VC_ENCODED,
|
|
313
|
-
format: 'vc+sd-jwt',
|
|
314
|
-
original: verifiableCredential,
|
|
315
|
-
credential: decodedCredential,
|
|
316
|
-
decoded: decodedCredential.decodedPayload,
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// If the VC is not an encoded/decoded SD-JWT, we assume it will be a W3C VC
|
|
321
|
-
const proof = CredentialMapper.getFirstProof(verifiableCredential)
|
|
322
|
-
const original = CredentialMapper.hasJWTProofType(verifiableCredential) && proof ? proof.jwt ?? verifiableCredential : verifiableCredential
|
|
323
|
-
if (!original) {
|
|
324
|
-
throw Error(
|
|
325
|
-
'Could not determine original credential, probably it was a converted JWT credential, that is now missing the JWT value in the proof'
|
|
326
|
-
)
|
|
327
|
-
}
|
|
328
|
-
const decoded = CredentialMapper.decodeVerifiableCredential(original) as JwtDecodedVerifiableCredential | IVerifiableCredential
|
|
329
|
-
|
|
330
|
-
const isJwtEncoded = CredentialMapper.isJwtEncoded(original)
|
|
331
|
-
const isJwtDecoded = CredentialMapper.isJwtDecodedCredential(original)
|
|
332
|
-
const type = isJwtEncoded ? OriginalType.JWT_ENCODED : isJwtDecoded ? OriginalType.JWT_DECODED : OriginalType.JSONLD
|
|
333
|
-
|
|
334
|
-
const credential =
|
|
335
|
-
isJwtEncoded || isJwtDecoded
|
|
336
|
-
? CredentialMapper.jwtDecodedCredentialToUniformCredential(decoded as JwtDecodedVerifiableCredential, opts)
|
|
337
|
-
: (decoded as IVerifiableCredential)
|
|
338
|
-
|
|
339
|
-
const format = isJwtEncoded || isJwtDecoded ? ('jwt_vc' as const) : ('ldp_vc' as const)
|
|
340
|
-
return {
|
|
341
|
-
original,
|
|
342
|
-
decoded,
|
|
343
|
-
format,
|
|
344
|
-
type,
|
|
345
|
-
credential,
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
public static isJwtEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
|
|
350
|
-
return ObjectUtils.isString(original) && original.startsWith('ey') && !original.includes('~')
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
public static isSdJwtEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
|
|
354
|
-
return ObjectUtils.isString(original) && original.startsWith('ey') && original.includes('~')
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
public static isMsoMdocOid4VPEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
|
|
358
|
-
return ObjectUtils.isString(original) && !original.startsWith('ey') && ObjectUtils.isBase64(original)
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
public static isW3cCredential(credential: ICredential | SdJwtDecodedVerifiableCredential): credential is ICredential {
|
|
362
|
-
return typeof credential === 'object' && '@context' in credential && ((credential as ICredential).type?.includes('VerifiableCredential') || false)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
public static isCredential(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is OriginalVerifiableCredential {
|
|
366
|
-
try {
|
|
367
|
-
if (CredentialMapper.isJwtEncoded(original)) {
|
|
368
|
-
const vc: IVerifiableCredential = CredentialMapper.toUniformCredential(original)
|
|
369
|
-
return CredentialMapper.isW3cCredential(vc)
|
|
370
|
-
} else if (CredentialMapper.isSdJwtEncoded(original)) {
|
|
371
|
-
return true
|
|
372
|
-
} else if (CredentialMapper.isMsoMdocDecodedCredential(original)) {
|
|
373
|
-
return true
|
|
374
|
-
} else if (CredentialMapper.isMsoMdocOid4VPEncoded(original)) {
|
|
375
|
-
return true
|
|
376
|
-
}
|
|
377
|
-
return (
|
|
378
|
-
CredentialMapper.isW3cCredential(original as ICredential) ||
|
|
379
|
-
CredentialMapper.isSdJwtDecodedCredentialPayload(original as ICredential) ||
|
|
380
|
-
CredentialMapper.isJwtDecodedCredential(original as OriginalVerifiableCredential) ||
|
|
381
|
-
CredentialMapper.isSdJwtDecodedCredential(original as OriginalVerifiableCredential)
|
|
382
|
-
)
|
|
383
|
-
} catch (e) {
|
|
384
|
-
return false
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
public static isPresentation(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is OriginalVerifiablePresentation {
|
|
389
|
-
try {
|
|
390
|
-
if (CredentialMapper.isJwtEncoded(original)) {
|
|
391
|
-
const vp: IVerifiablePresentation = CredentialMapper.toUniformPresentation(original)
|
|
392
|
-
return CredentialMapper.isW3cPresentation(vp)
|
|
393
|
-
} else if (CredentialMapper.isSdJwtEncoded(original)) {
|
|
394
|
-
return false
|
|
395
|
-
// @ts-expect-error
|
|
396
|
-
} else if (CredentialMapper.isMsoMdocDecodedPresentation(original)) {
|
|
397
|
-
return true
|
|
398
|
-
} else if (CredentialMapper.isMsoMdocOid4VPEncoded(original)) {
|
|
399
|
-
return true
|
|
400
|
-
}
|
|
401
|
-
return (
|
|
402
|
-
CredentialMapper.isW3cPresentation(original as IPresentation) ||
|
|
403
|
-
CredentialMapper.isSdJwtDecodedCredentialPayload(original as ICredential) ||
|
|
404
|
-
CredentialMapper.isJwtDecodedPresentation(original as OriginalVerifiablePresentation) ||
|
|
405
|
-
CredentialMapper.isSdJwtDecodedCredential(original as OriginalVerifiableCredential)
|
|
406
|
-
)
|
|
407
|
-
} catch (e) {
|
|
408
|
-
return false
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
public static hasProof(original: OriginalVerifiableCredential | OriginalVerifiablePresentation | string): boolean {
|
|
413
|
-
try {
|
|
414
|
-
if (CredentialMapper.isMsoMdocOid4VPEncoded(original)) {
|
|
415
|
-
return false
|
|
416
|
-
// @ts-ignore
|
|
417
|
-
} else if (CredentialMapper.isMsoMdocDecodedCredential(original) || CredentialMapper.isMsoMdocDecodedPresentation(original)) {
|
|
418
|
-
return true
|
|
419
|
-
} else if (CredentialMapper.isJwtEncoded(original) || CredentialMapper.isJwtDecodedCredential(original as OriginalVerifiableCredential)) {
|
|
420
|
-
return true
|
|
421
|
-
} else if (CredentialMapper.isSdJwtEncoded(original) || CredentialMapper.isSdJwtDecodedCredential(original)) {
|
|
422
|
-
//todo: we might want to revisit this
|
|
423
|
-
return true
|
|
424
|
-
}
|
|
425
|
-
if (typeof original !== 'object') {
|
|
426
|
-
return false
|
|
427
|
-
}
|
|
428
|
-
if ('vc' in (original as JwtDecodedVerifiableCredential) && (original as JwtDecodedVerifiableCredential).vc.proof) {
|
|
429
|
-
return true
|
|
430
|
-
}
|
|
431
|
-
if ('vp' in (original as JwtDecodedVerifiablePresentation) && (original as JwtDecodedVerifiablePresentation).vp.proof) {
|
|
432
|
-
return true
|
|
433
|
-
}
|
|
434
|
-
return !!(original as IVerifiableCredential | IVerifiablePresentation).proof
|
|
435
|
-
} catch (e) {
|
|
436
|
-
return false
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
public static isW3cPresentation(
|
|
441
|
-
presentation: UniformVerifiablePresentation | IPresentation | SdJwtDecodedVerifiableCredential | DeviceResponseCbor
|
|
442
|
-
): presentation is IPresentation {
|
|
443
|
-
return (
|
|
444
|
-
typeof presentation === 'object' &&
|
|
445
|
-
'@context' in presentation &&
|
|
446
|
-
((presentation as IPresentation).type?.includes('VerifiablePresentation') || false)
|
|
447
|
-
)
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
public static isSdJwtDecodedCredentialPayload(
|
|
451
|
-
credential: ICredential | SdJwtDecodedVerifiableCredentialPayload
|
|
452
|
-
): credential is SdJwtDecodedVerifiableCredentialPayload {
|
|
453
|
-
return typeof credential === 'object' && 'vct' in credential
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
public static areOriginalVerifiableCredentialsEqual(firstOriginal: OriginalVerifiableCredential, secondOriginal: OriginalVerifiableCredential) {
|
|
457
|
-
// String (e.g. encoded jwt or SD-JWT)
|
|
458
|
-
if (typeof firstOriginal === 'string' || typeof secondOriginal === 'string') {
|
|
459
|
-
return firstOriginal === secondOriginal
|
|
460
|
-
} else if (CredentialMapper.isMsoMdocDecodedCredential(firstOriginal) || CredentialMapper.isMsoMdocDecodedCredential(secondOriginal)) {
|
|
461
|
-
if (!CredentialMapper.isMsoMdocDecodedCredential(firstOriginal) || !CredentialMapper.isMsoMdocDecodedCredential(secondOriginal)) {
|
|
462
|
-
// We are doing this over here, as the rest of the logic around it would otherwise need to be adjusted substantially
|
|
463
|
-
return false
|
|
464
|
-
}
|
|
465
|
-
return firstOriginal.toJson().toJsonString() === secondOriginal.toJson().toJsonString()
|
|
466
|
-
} else if (CredentialMapper.isSdJwtDecodedCredential(firstOriginal) || CredentialMapper.isSdJwtDecodedCredential(secondOriginal)) {
|
|
467
|
-
return firstOriginal.compactSdJwtVc === secondOriginal.compactSdJwtVc
|
|
468
|
-
} else {
|
|
469
|
-
// JSON-LD or decoded JWT. (should we compare the signatures instead?)
|
|
470
|
-
return JSON.stringify(secondOriginal.proof) === JSON.stringify(firstOriginal.proof)
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
private static isJsonLdAsString(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
|
|
475
|
-
return ObjectUtils.isString(original) && original.includes('@context')
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
public static isSdJwtDecodedCredential(
|
|
479
|
-
original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation
|
|
480
|
-
): original is SdJwtDecodedVerifiableCredential {
|
|
481
|
-
return (
|
|
482
|
-
typeof original === 'object' &&
|
|
483
|
-
((<SdJwtDecodedVerifiableCredential>original).compactSdJwtVc !== undefined || (<SdJwtDecodedVerifiableCredential>original).kbJwt !== undefined)
|
|
484
|
-
)
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
public static isMsoMdocDecodedCredential(
|
|
488
|
-
original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation
|
|
489
|
-
): original is MdocDocument {
|
|
490
|
-
return typeof original === 'object' && 'issuerSigned' in original && (<MdocDocument>original).issuerSigned !== undefined
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
public static isMsoMdocDecodedPresentation(original: OriginalVerifiablePresentation): original is MdocDeviceResponse {
|
|
494
|
-
return typeof original === 'object' && 'version' in original && (<MdocDeviceResponse>original).version !== undefined
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
public static isJwtDecodedCredential(original: OriginalVerifiableCredential): original is JwtDecodedVerifiableCredential {
|
|
498
|
-
return (
|
|
499
|
-
typeof original === 'object' &&
|
|
500
|
-
(<JwtDecodedVerifiableCredential>original).vc !== undefined &&
|
|
501
|
-
(<JwtDecodedVerifiableCredential>original).iss !== undefined
|
|
502
|
-
)
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
public static isJwtDecodedPresentation(original: OriginalVerifiablePresentation): original is JwtDecodedVerifiablePresentation {
|
|
506
|
-
return (
|
|
507
|
-
typeof original === 'object' &&
|
|
508
|
-
(<JwtDecodedVerifiablePresentation>original).vp !== undefined &&
|
|
509
|
-
(<JwtDecodedVerifiablePresentation>original).iss !== undefined
|
|
510
|
-
)
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
public static isWrappedSdJwtVerifiableCredential = isWrappedSdJwtVerifiableCredential
|
|
514
|
-
public static isWrappedSdJwtVerifiablePresentation = isWrappedSdJwtVerifiablePresentation
|
|
515
|
-
public static isWrappedW3CVerifiableCredential = isWrappedW3CVerifiableCredential
|
|
516
|
-
public static isWrappedW3CVerifiablePresentation = isWrappedW3CVerifiablePresentation
|
|
517
|
-
public static isWrappedMdocCredential = isWrappedMdocCredential
|
|
518
|
-
public static isWrappedMdocPresentation = isWrappedMdocPresentation
|
|
519
|
-
|
|
520
|
-
static jwtEncodedPresentationToUniformPresentation(
|
|
521
|
-
jwt: string,
|
|
522
|
-
makeCredentialsUniform: boolean = true,
|
|
523
|
-
opts?: { maxTimeSkewInMS?: number }
|
|
524
|
-
): IPresentation {
|
|
525
|
-
return CredentialMapper.jwtDecodedPresentationToUniformPresentation(jwt_decode(jwt), makeCredentialsUniform, opts)
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
static jwtDecodedPresentationToUniformPresentation(
|
|
529
|
-
decoded: JwtDecodedVerifiablePresentation,
|
|
530
|
-
makeCredentialsUniform: boolean = true,
|
|
531
|
-
opts?: { maxTimeSkewInMS?: number }
|
|
532
|
-
): IVerifiablePresentation {
|
|
533
|
-
const { iss, aud, jti, vp, ...rest } = decoded
|
|
534
|
-
|
|
535
|
-
const presentation: IVerifiablePresentation = {
|
|
536
|
-
...rest,
|
|
537
|
-
...vp,
|
|
538
|
-
}
|
|
539
|
-
if (makeCredentialsUniform) {
|
|
540
|
-
if (!vp.verifiableCredential) {
|
|
541
|
-
throw Error('Verifiable Presentation should have a verifiable credential at this point')
|
|
542
|
-
}
|
|
543
|
-
presentation.verifiableCredential = vp.verifiableCredential.map((vc) => CredentialMapper.toUniformCredential(vc, opts))
|
|
544
|
-
}
|
|
545
|
-
if (iss) {
|
|
546
|
-
const holder = presentation.holder
|
|
547
|
-
if (holder) {
|
|
548
|
-
if (holder !== iss) {
|
|
549
|
-
throw new Error(`Inconsistent holders between JWT claim (${iss}) and VC value (${holder})`)
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
presentation.holder = iss
|
|
553
|
-
}
|
|
554
|
-
if (aud) {
|
|
555
|
-
const verifier = presentation.verifier
|
|
556
|
-
if (verifier) {
|
|
557
|
-
if (verifier !== aud) {
|
|
558
|
-
throw new Error(`Inconsistent holders between JWT claim (${aud}) and VC value (${verifier})`)
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
presentation.verifier = aud
|
|
562
|
-
}
|
|
563
|
-
if (jti) {
|
|
564
|
-
const id = presentation.id
|
|
565
|
-
if (id && id !== jti) {
|
|
566
|
-
throw new Error(`Inconsistent VP ids between JWT claim (${jti}) and VP value (${id})`)
|
|
567
|
-
}
|
|
568
|
-
presentation.id = jti
|
|
569
|
-
}
|
|
570
|
-
return presentation
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
static toUniformCredential(
|
|
574
|
-
verifiableCredential: OriginalVerifiableCredential,
|
|
575
|
-
opts?: {
|
|
576
|
-
maxTimeSkewInMS?: number
|
|
577
|
-
hasher?: Hasher
|
|
578
|
-
}
|
|
579
|
-
): IVerifiableCredential {
|
|
580
|
-
if (CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential)) {
|
|
581
|
-
return mdocDecodedCredentialToUniformCredential(verifiableCredential)
|
|
582
|
-
}
|
|
583
|
-
if (CredentialMapper.isSdJwtDecodedCredential(verifiableCredential)) {
|
|
584
|
-
return sdJwtDecodedCredentialToUniformCredential(verifiableCredential, opts)
|
|
585
|
-
}
|
|
586
|
-
const original =
|
|
587
|
-
typeof verifiableCredential !== 'string' && CredentialMapper.hasJWTProofType(verifiableCredential)
|
|
588
|
-
? CredentialMapper.getFirstProof(verifiableCredential)?.jwt
|
|
589
|
-
: verifiableCredential
|
|
590
|
-
if (!original) {
|
|
591
|
-
throw Error(
|
|
592
|
-
'Could not determine original credential from passed in credential. Probably because a JWT proof type was present, but now is not available anymore'
|
|
593
|
-
)
|
|
594
|
-
}
|
|
595
|
-
const decoded = CredentialMapper.decodeVerifiableCredential(original, opts?.hasher)
|
|
596
|
-
|
|
597
|
-
const isJwtEncoded: boolean = CredentialMapper.isJwtEncoded(original)
|
|
598
|
-
const isJwtDecoded: boolean = CredentialMapper.isJwtDecodedCredential(original)
|
|
599
|
-
const isSdJwtEncoded = CredentialMapper.isSdJwtEncoded(original)
|
|
600
|
-
const isMdocEncoded = CredentialMapper.isMsoMdocOid4VPEncoded(original)
|
|
601
|
-
|
|
602
|
-
if (isSdJwtEncoded) {
|
|
603
|
-
return sdJwtDecodedCredentialToUniformCredential(decoded as SdJwtDecodedVerifiableCredential, opts)
|
|
604
|
-
} else if (isMdocEncoded) {
|
|
605
|
-
return mdocDecodedCredentialToUniformCredential(decodeMdocIssuerSigned(original))
|
|
606
|
-
} else if (isJwtDecoded || isJwtEncoded) {
|
|
607
|
-
return CredentialMapper.jwtDecodedCredentialToUniformCredential(decoded as JwtDecodedVerifiableCredential, opts)
|
|
608
|
-
} else {
|
|
609
|
-
return decoded as IVerifiableCredential
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
static toUniformPresentation(
|
|
614
|
-
presentation: OriginalVerifiablePresentation,
|
|
615
|
-
opts?: { maxTimeSkewInMS?: number; addContextIfMissing?: boolean }
|
|
616
|
-
): IVerifiablePresentation {
|
|
617
|
-
if (CredentialMapper.isSdJwtDecodedCredential(presentation)) {
|
|
618
|
-
throw new Error('Converting SD-JWT VC to uniform VP is not supported.')
|
|
619
|
-
} else if (CredentialMapper.isMsoMdocDecodedPresentation(presentation)) {
|
|
620
|
-
throw new Error('Converting MSO_MDOC to uniform VP is not supported yet.')
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
const proof = CredentialMapper.getFirstProof(presentation)
|
|
624
|
-
const original = typeof presentation !== 'string' && CredentialMapper.hasJWTProofType(presentation) ? proof?.jwt : presentation
|
|
625
|
-
if (!original) {
|
|
626
|
-
throw Error(
|
|
627
|
-
'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof'
|
|
628
|
-
)
|
|
629
|
-
}
|
|
630
|
-
const decoded = CredentialMapper.decodeVerifiablePresentation(original)
|
|
631
|
-
const isJwtEncoded: boolean = CredentialMapper.isJwtEncoded(original)
|
|
632
|
-
const isJwtDecoded: boolean = CredentialMapper.isJwtDecodedPresentation(original)
|
|
633
|
-
const uniformPresentation =
|
|
634
|
-
isJwtEncoded || isJwtDecoded
|
|
635
|
-
? CredentialMapper.jwtDecodedPresentationToUniformPresentation(decoded as JwtDecodedVerifiablePresentation, false)
|
|
636
|
-
: (decoded as IVerifiablePresentation)
|
|
637
|
-
|
|
638
|
-
// At time of writing Velocity Networks does not conform to specification. Adding bare minimum @context section to stop parsers from crashing and whatnot
|
|
639
|
-
if (opts?.addContextIfMissing && !uniformPresentation['@context']) {
|
|
640
|
-
uniformPresentation['@context'] = ['https://www.w3.org/2018/credentials/v1']
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
uniformPresentation.verifiableCredential = uniformPresentation.verifiableCredential?.map((vc) =>
|
|
644
|
-
CredentialMapper.toUniformCredential(vc, opts)
|
|
645
|
-
) as IVerifiableCredential[] // We cast it because we IPresentation needs a VC. The internal Credential doesn't have the required Proof anymore (that is intended)
|
|
646
|
-
return uniformPresentation
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
static jwtEncodedCredentialToUniformCredential(
|
|
650
|
-
jwt: string,
|
|
651
|
-
opts?: {
|
|
652
|
-
maxTimeSkewInMS?: number
|
|
653
|
-
}
|
|
654
|
-
): IVerifiableCredential {
|
|
655
|
-
return CredentialMapper.jwtDecodedCredentialToUniformCredential(jwt_decode(jwt), opts)
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
static jwtDecodedCredentialToUniformCredential(
|
|
659
|
-
decoded: JwtDecodedVerifiableCredential,
|
|
660
|
-
opts?: { maxTimeSkewInMS?: number }
|
|
661
|
-
): IVerifiableCredential {
|
|
662
|
-
const { exp, nbf, iss, vc, sub, jti, ...rest } = decoded
|
|
663
|
-
const credential: IVerifiableCredential = {
|
|
664
|
-
...rest,
|
|
665
|
-
...vc,
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
const maxSkewInMS = opts?.maxTimeSkewInMS ?? 1500
|
|
669
|
-
|
|
670
|
-
if (exp) {
|
|
671
|
-
const expDate = credential.expirationDate
|
|
672
|
-
const jwtExp = parseInt(exp.toString())
|
|
673
|
-
// fix seconds to millisecond for the date
|
|
674
|
-
const expDateAsStr = jwtExp < 9999999999 ? new Date(jwtExp * 1000).toISOString().replace(/\.000Z/, 'Z') : new Date(jwtExp).toISOString()
|
|
675
|
-
if (expDate && expDate !== expDateAsStr) {
|
|
676
|
-
const diff = Math.abs(new Date(expDateAsStr).getTime() - new Date(expDate).getTime())
|
|
677
|
-
if (!maxSkewInMS || diff > maxSkewInMS) {
|
|
678
|
-
throw new Error(`Inconsistent expiration dates between JWT claim (${expDateAsStr}) and VC value (${expDate})`)
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
credential.expirationDate = expDateAsStr
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
if (nbf) {
|
|
685
|
-
const issuanceDate = credential.issuanceDate
|
|
686
|
-
const jwtNbf = parseInt(nbf.toString())
|
|
687
|
-
// fix seconds to millisecs for the date
|
|
688
|
-
const nbfDateAsStr = jwtNbf < 9999999999 ? new Date(jwtNbf * 1000).toISOString().replace(/\.000Z/, 'Z') : new Date(jwtNbf).toISOString()
|
|
689
|
-
if (issuanceDate && issuanceDate !== nbfDateAsStr) {
|
|
690
|
-
const diff = Math.abs(new Date(nbfDateAsStr).getTime() - new Date(issuanceDate).getTime())
|
|
691
|
-
if (!maxSkewInMS || diff > maxSkewInMS) {
|
|
692
|
-
throw new Error(`Inconsistent issuance dates between JWT claim (${nbfDateAsStr}) and VC value (${issuanceDate})`)
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
credential.issuanceDate = nbfDateAsStr
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
if (iss) {
|
|
699
|
-
const issuer = credential.issuer
|
|
700
|
-
if (issuer) {
|
|
701
|
-
if (typeof issuer === 'string') {
|
|
702
|
-
if (issuer !== iss) {
|
|
703
|
-
throw new Error(`Inconsistent issuers between JWT claim (${iss}) and VC value (${issuer})`)
|
|
704
|
-
}
|
|
705
|
-
} else {
|
|
706
|
-
if (!issuer.id && Object.keys(issuer).length > 0) {
|
|
707
|
-
// We have an issuer object with more than 1 property but without an issuer id. Set it,
|
|
708
|
-
// because the default behaviour of did-jwt-vc is to remove the id value when creating JWTs
|
|
709
|
-
issuer.id = iss
|
|
710
|
-
}
|
|
711
|
-
if (issuer.id !== iss) {
|
|
712
|
-
throw new Error(`Inconsistent issuers between JWT claim (${iss}) and VC value (${issuer.id})`)
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
} else {
|
|
716
|
-
credential.issuer = iss
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
if (sub) {
|
|
721
|
-
const subjects = Array.isArray(credential.credentialSubject) ? credential.credentialSubject : [credential.credentialSubject]
|
|
722
|
-
for (let i = 0; i < subjects.length; i++) {
|
|
723
|
-
const csId = subjects[i].id
|
|
724
|
-
if (csId && csId !== sub) {
|
|
725
|
-
throw new Error(`Inconsistent credential subject ids between JWT claim (${sub}) and VC value (${csId})`)
|
|
726
|
-
}
|
|
727
|
-
Array.isArray(credential.credentialSubject) ? (credential.credentialSubject[i].id = sub) : (credential.credentialSubject.id = sub)
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
if (jti) {
|
|
731
|
-
const id = credential.id
|
|
732
|
-
if (id && id !== jti) {
|
|
733
|
-
throw new Error(`Inconsistent credential ids between JWT claim (${jti}) and VC value (${id})`)
|
|
734
|
-
}
|
|
735
|
-
credential.id = jti
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
return credential
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
static toExternalVerifiableCredential(verifiableCredential: any): IVerifiableCredential {
|
|
742
|
-
let proof
|
|
743
|
-
if (verifiableCredential.proof) {
|
|
744
|
-
if (!verifiableCredential.proof.type) {
|
|
745
|
-
throw new Error('Verifiable credential proof is missing a type')
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
if (!verifiableCredential.proof.created) {
|
|
749
|
-
throw new Error('Verifiable credential proof is missing a created date')
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
if (!verifiableCredential.proof.proofPurpose) {
|
|
753
|
-
throw new Error('Verifiable credential proof is missing a proof purpose')
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
if (!verifiableCredential.proof.verificationMethod) {
|
|
757
|
-
throw new Error('Verifiable credential proof is missing a verification method')
|
|
758
|
-
}
|
|
759
|
-
proof = {
|
|
760
|
-
...verifiableCredential.proof,
|
|
761
|
-
type: verifiableCredential.proof.type,
|
|
762
|
-
created: verifiableCredential.proof.created,
|
|
763
|
-
proofPurpose: verifiableCredential.proof.proofPurpose,
|
|
764
|
-
verificationMethod: verifiableCredential.proof.verificationMethod,
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
return {
|
|
769
|
-
...verifiableCredential,
|
|
770
|
-
type: verifiableCredential.type
|
|
771
|
-
? typeof verifiableCredential.type === 'string'
|
|
772
|
-
? [verifiableCredential.type]
|
|
773
|
-
: verifiableCredential.type
|
|
774
|
-
: ['VerifiableCredential'],
|
|
775
|
-
proof,
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
static storedCredentialToOriginalFormat(credential: OriginalVerifiableCredential): W3CVerifiableCredential {
|
|
780
|
-
const type: DocumentFormat = CredentialMapper.detectDocumentType(credential)
|
|
781
|
-
if (typeof credential === 'string') {
|
|
782
|
-
if (type === DocumentFormat.JWT) {
|
|
783
|
-
return CredentialMapper.toCompactJWT(credential)
|
|
784
|
-
} else if (type === DocumentFormat.JSONLD) {
|
|
785
|
-
return JSON.parse(credential)
|
|
786
|
-
} else if (type === DocumentFormat.SD_JWT_VC) {
|
|
787
|
-
return credential
|
|
788
|
-
}
|
|
789
|
-
} else if (type === DocumentFormat.JWT && ObjectUtils.isObject(credential) && 'vc' in credential) {
|
|
790
|
-
return CredentialMapper.toCompactJWT(credential)
|
|
791
|
-
} else if (ObjectUtils.isObject(credential) && 'proof' in credential && credential.proof.type === 'JwtProof2020' && credential.proof.jwt) {
|
|
792
|
-
return credential.proof.jwt
|
|
793
|
-
} else if (
|
|
794
|
-
ObjectUtils.isObject(credential) &&
|
|
795
|
-
'proof' in credential &&
|
|
796
|
-
credential.proof.type === IProofType.SdJwtProof2024 &&
|
|
797
|
-
credential.proof.jwt
|
|
798
|
-
) {
|
|
799
|
-
return credential.proof.jwt
|
|
800
|
-
} else if (type === DocumentFormat.SD_JWT_VC && this.isSdJwtDecodedCredential(credential)) {
|
|
801
|
-
return credential.compactSdJwtVc
|
|
802
|
-
}
|
|
803
|
-
return credential as W3CVerifiableCredential
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
static storedPresentationToOriginalFormat(presentation: OriginalVerifiablePresentation): W3CVerifiablePresentation {
|
|
807
|
-
const type: DocumentFormat = CredentialMapper.detectDocumentType(presentation)
|
|
808
|
-
if (typeof presentation === 'string') {
|
|
809
|
-
if (type === DocumentFormat.JWT) {
|
|
810
|
-
return CredentialMapper.toCompactJWT(presentation)
|
|
811
|
-
} else if (type === DocumentFormat.JSONLD) {
|
|
812
|
-
return JSON.parse(presentation)
|
|
813
|
-
}
|
|
814
|
-
} else if (type === DocumentFormat.JWT && ObjectUtils.isObject(presentation) && 'vp' in presentation) {
|
|
815
|
-
return CredentialMapper.toCompactJWT(presentation)
|
|
816
|
-
} else if (
|
|
817
|
-
ObjectUtils.isObject(presentation) &&
|
|
818
|
-
'proof' in presentation &&
|
|
819
|
-
presentation.proof.type === 'JwtProof2020' &&
|
|
820
|
-
presentation.proof.jwt
|
|
821
|
-
) {
|
|
822
|
-
return presentation.proof.jwt
|
|
823
|
-
}
|
|
824
|
-
return presentation as W3CVerifiablePresentation
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
static toCompactJWT(
|
|
828
|
-
jwtDocument: W3CVerifiableCredential | JwtDecodedVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiablePresentation | string
|
|
829
|
-
): string {
|
|
830
|
-
if (!jwtDocument || CredentialMapper.detectDocumentType(jwtDocument) !== DocumentFormat.JWT) {
|
|
831
|
-
throw Error('Cannot convert non JWT credential to JWT')
|
|
832
|
-
}
|
|
833
|
-
if (typeof jwtDocument === 'string') {
|
|
834
|
-
return jwtDocument
|
|
835
|
-
}
|
|
836
|
-
let proof: string | undefined
|
|
837
|
-
if (ObjectUtils.isObject(jwtDocument) && 'vp' in jwtDocument) {
|
|
838
|
-
proof = 'jwt' in jwtDocument.vp.proof ? jwtDocument.vp.proof.jwt : jwtDocument.vp.proof
|
|
839
|
-
} else if (ObjectUtils.isObject(jwtDocument) && 'vc' in jwtDocument) {
|
|
840
|
-
proof = 'jwt' in jwtDocument.vc.proof ? jwtDocument.vc.proof.jwt : jwtDocument.vc.proof
|
|
841
|
-
} else {
|
|
842
|
-
proof = Array.isArray(jwtDocument.proof) ? jwtDocument.proof[0].jwt : jwtDocument.proof.jwt
|
|
843
|
-
}
|
|
844
|
-
if (!proof) {
|
|
845
|
-
throw Error(`Could not get JWT from supplied document`)
|
|
846
|
-
}
|
|
847
|
-
return proof
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
static detectDocumentType(
|
|
851
|
-
document:
|
|
852
|
-
| W3CVerifiableCredential
|
|
853
|
-
| W3CVerifiablePresentation
|
|
854
|
-
| JwtDecodedVerifiableCredential
|
|
855
|
-
| JwtDecodedVerifiablePresentation
|
|
856
|
-
| SdJwtDecodedVerifiableCredential
|
|
857
|
-
| MdocDeviceResponse
|
|
858
|
-
| MdocDocument
|
|
859
|
-
): DocumentFormat {
|
|
860
|
-
if (this.isMsoMdocOid4VPEncoded(document as any) || this.isMsoMdocDecodedCredential(document as any)) {
|
|
861
|
-
return DocumentFormat.MSO_MDOC
|
|
862
|
-
} else if (this.isMsoMdocDecodedPresentation(document as any) || this.isMsoMdocDecodedPresentation(document as any)) {
|
|
863
|
-
return DocumentFormat.MSO_MDOC
|
|
864
|
-
} else if (this.isJsonLdAsString(document)) {
|
|
865
|
-
return DocumentFormat.JSONLD
|
|
866
|
-
} else if (this.isJwtEncoded(document)) {
|
|
867
|
-
return DocumentFormat.JWT
|
|
868
|
-
} else if (this.isSdJwtEncoded(document) || this.isSdJwtDecodedCredential(document as any)) {
|
|
869
|
-
return DocumentFormat.SD_JWT_VC
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
const proofs =
|
|
873
|
-
typeof document !== 'string' &&
|
|
874
|
-
('vc' in document ? document.vc.proof : 'vp' in document ? document.vp.proof : (<IVerifiableCredential>document).proof)
|
|
875
|
-
const proof: IProof = Array.isArray(proofs) ? proofs[0] : proofs
|
|
876
|
-
|
|
877
|
-
if (proof?.jwt) {
|
|
878
|
-
return DocumentFormat.JWT
|
|
879
|
-
} else if (proof?.type === 'EthereumEip712Signature2021') {
|
|
880
|
-
return DocumentFormat.EIP712
|
|
881
|
-
}
|
|
882
|
-
return DocumentFormat.JSONLD
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
private static hasJWTProofType(
|
|
886
|
-
document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation
|
|
887
|
-
): boolean {
|
|
888
|
-
if (typeof document === 'string') {
|
|
889
|
-
return false
|
|
890
|
-
}
|
|
891
|
-
return !!CredentialMapper.getFirstProof(document)?.jwt
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
private static getFirstProof(
|
|
895
|
-
document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation
|
|
896
|
-
): IProof | undefined {
|
|
897
|
-
if (!document || typeof document === 'string') {
|
|
898
|
-
return undefined
|
|
899
|
-
}
|
|
900
|
-
const proofs = 'vc' in document ? document.vc.proof : 'vp' in document ? document.vp.proof : (<IVerifiableCredential>document).proof
|
|
901
|
-
return Array.isArray(proofs) ? proofs[0] : proofs
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
static issuerCorrelationIdFromIssuerType(issuer: IssuerType): string {
|
|
905
|
-
if (issuer === undefined) {
|
|
906
|
-
throw new Error('Issuer type us undefined')
|
|
907
|
-
} else if (typeof issuer === 'string') {
|
|
908
|
-
return issuer
|
|
909
|
-
} else if (typeof issuer === 'object') {
|
|
910
|
-
if ('id' in issuer) {
|
|
911
|
-
return issuer.id
|
|
912
|
-
} else {
|
|
913
|
-
throw new Error('Encountered an invalid issuer object: missing id property')
|
|
914
|
-
}
|
|
915
|
-
} else {
|
|
916
|
-
throw new Error('Invalid issuer type')
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
}
|
|
1
|
+
import { IssuerType } from '@veramo/core'
|
|
2
|
+
import jwt_decode from 'jwt-decode'
|
|
3
|
+
import {
|
|
4
|
+
AsyncHasher,
|
|
5
|
+
decodeMdocDeviceResponse,
|
|
6
|
+
decodeMdocIssuerSigned,
|
|
7
|
+
decodeSdJwtVc,
|
|
8
|
+
decodeSdJwtVcAsync,
|
|
9
|
+
DocumentFormat,
|
|
10
|
+
Hasher,
|
|
11
|
+
ICredential,
|
|
12
|
+
IPresentation,
|
|
13
|
+
IProof,
|
|
14
|
+
IProofPurpose,
|
|
15
|
+
IProofType,
|
|
16
|
+
isWrappedMdocCredential,
|
|
17
|
+
isWrappedMdocPresentation,
|
|
18
|
+
isWrappedSdJwtVerifiableCredential,
|
|
19
|
+
isWrappedSdJwtVerifiablePresentation,
|
|
20
|
+
isWrappedW3CVerifiableCredential,
|
|
21
|
+
isWrappedW3CVerifiablePresentation,
|
|
22
|
+
IVerifiableCredential,
|
|
23
|
+
IVerifiablePresentation,
|
|
24
|
+
JwtDecodedVerifiableCredential,
|
|
25
|
+
JwtDecodedVerifiablePresentation,
|
|
26
|
+
mdocDecodedCredentialToUniformCredential,
|
|
27
|
+
MdocDeviceResponse,
|
|
28
|
+
MdocOid4vpMdocVpToken,
|
|
29
|
+
OriginalType,
|
|
30
|
+
OriginalVerifiableCredential,
|
|
31
|
+
OriginalVerifiablePresentation,
|
|
32
|
+
sdJwtDecodedCredentialToUniformCredential,
|
|
33
|
+
SdJwtDecodedVerifiableCredential,
|
|
34
|
+
SdJwtDecodedVerifiableCredentialPayload,
|
|
35
|
+
UniformVerifiablePresentation,
|
|
36
|
+
W3CVerifiableCredential,
|
|
37
|
+
W3CVerifiablePresentation,
|
|
38
|
+
WrappedMdocCredential,
|
|
39
|
+
WrappedSdJwtVerifiableCredential,
|
|
40
|
+
WrappedVerifiableCredential,
|
|
41
|
+
WrappedVerifiablePresentation,
|
|
42
|
+
WrappedW3CVerifiableCredential,
|
|
43
|
+
} from '../types'
|
|
44
|
+
import { MdocDocument } from '../types/mso_mdoc'
|
|
45
|
+
import { ObjectUtils } from '../utils'
|
|
46
|
+
import { com } from '@sphereon/kmp-mdl-mdoc'
|
|
47
|
+
import DeviceResponseCbor = com.sphereon.mdoc.data.device.DeviceResponseCbor
|
|
48
|
+
|
|
49
|
+
export class CredentialMapper {
|
|
50
|
+
/**
|
|
51
|
+
* Decodes a compact SD-JWT vc to it's decoded variant. This method can be used when the hasher implementation used is Async, and therefore not suitable for usage
|
|
52
|
+
* with the other decode methods.
|
|
53
|
+
*/
|
|
54
|
+
static decodeSdJwtVcAsync(compactSdJwtVc: string, hasher: AsyncHasher) {
|
|
55
|
+
return decodeSdJwtVcAsync(compactSdJwtVc, hasher)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Decodes a Verifiable Presentation to a uniform format.
|
|
60
|
+
*
|
|
61
|
+
* When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
|
|
62
|
+
* an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
|
|
63
|
+
* instead of the compact SD-JWT.
|
|
64
|
+
*
|
|
65
|
+
* @param hasher Hasher implementation to use for SD-JWT decoding.
|
|
66
|
+
*/
|
|
67
|
+
static decodeVerifiablePresentation(
|
|
68
|
+
presentation: OriginalVerifiablePresentation,
|
|
69
|
+
hasher?: Hasher
|
|
70
|
+
): JwtDecodedVerifiablePresentation | IVerifiablePresentation | SdJwtDecodedVerifiableCredential | MdocOid4vpMdocVpToken | MdocDeviceResponse {
|
|
71
|
+
if (CredentialMapper.isJwtEncoded(presentation)) {
|
|
72
|
+
const payload = jwt_decode(presentation as string) as JwtDecodedVerifiablePresentation
|
|
73
|
+
const header = jwt_decode(presentation as string, { header: true }) as Record<string, any>
|
|
74
|
+
|
|
75
|
+
payload.vp.proof = {
|
|
76
|
+
type: IProofType.JwtProof2020,
|
|
77
|
+
created: payload.nbf,
|
|
78
|
+
proofPurpose: IProofPurpose.authentication,
|
|
79
|
+
verificationMethod: header['kid'] ?? payload.iss,
|
|
80
|
+
jwt: presentation as string,
|
|
81
|
+
}
|
|
82
|
+
return payload
|
|
83
|
+
} else if (CredentialMapper.isJwtDecodedPresentation(presentation)) {
|
|
84
|
+
return presentation as JwtDecodedVerifiablePresentation
|
|
85
|
+
} else if (CredentialMapper.isSdJwtEncoded(presentation)) {
|
|
86
|
+
if (!hasher) {
|
|
87
|
+
throw new Error('Hasher implementation is required to decode SD-JWT')
|
|
88
|
+
}
|
|
89
|
+
return decodeSdJwtVc(presentation, hasher)
|
|
90
|
+
} else if (CredentialMapper.isSdJwtDecodedCredential(presentation)) {
|
|
91
|
+
return presentation as SdJwtDecodedVerifiableCredential
|
|
92
|
+
} else if (CredentialMapper.isMsoMdocOid4VPEncoded(presentation)) {
|
|
93
|
+
return presentation as MdocOid4vpMdocVpToken
|
|
94
|
+
} else if (CredentialMapper.isMsoMdocDecodedPresentation(presentation)) {
|
|
95
|
+
return presentation as MdocDeviceResponse
|
|
96
|
+
} else if (CredentialMapper.isJsonLdAsString(presentation)) {
|
|
97
|
+
return JSON.parse(presentation as string) as IVerifiablePresentation
|
|
98
|
+
} else {
|
|
99
|
+
return presentation as IVerifiablePresentation
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Decodes a Verifiable Credential to a uniform format.
|
|
105
|
+
*
|
|
106
|
+
* When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
|
|
107
|
+
* an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
|
|
108
|
+
* instead of the compact SD-JWT.
|
|
109
|
+
*
|
|
110
|
+
* @param hasher Hasher implementation to use for SD-JWT decoding
|
|
111
|
+
*/
|
|
112
|
+
static decodeVerifiableCredential(
|
|
113
|
+
credential: OriginalVerifiableCredential,
|
|
114
|
+
hasher?: Hasher
|
|
115
|
+
): JwtDecodedVerifiableCredential | IVerifiableCredential | SdJwtDecodedVerifiableCredential {
|
|
116
|
+
if (CredentialMapper.isJwtEncoded(credential)) {
|
|
117
|
+
const payload = jwt_decode(credential as string) as JwtDecodedVerifiableCredential
|
|
118
|
+
const header = jwt_decode(credential as string, { header: true }) as Record<string, any>
|
|
119
|
+
payload.vc.proof = {
|
|
120
|
+
type: IProofType.JwtProof2020,
|
|
121
|
+
created: payload.nbf,
|
|
122
|
+
proofPurpose: IProofPurpose.authentication,
|
|
123
|
+
verificationMethod: header['kid'] ?? payload.iss,
|
|
124
|
+
jwt: credential as string,
|
|
125
|
+
}
|
|
126
|
+
return payload
|
|
127
|
+
} else if (CredentialMapper.isJwtDecodedCredential(credential)) {
|
|
128
|
+
return credential
|
|
129
|
+
} else if (CredentialMapper.isJsonLdAsString(credential)) {
|
|
130
|
+
return JSON.parse(credential as string) as IVerifiableCredential
|
|
131
|
+
} else if (CredentialMapper.isSdJwtEncoded(credential)) {
|
|
132
|
+
if (!hasher) {
|
|
133
|
+
throw new Error('Hasher implementation is required to decode SD-JWT')
|
|
134
|
+
}
|
|
135
|
+
return decodeSdJwtVc(credential, hasher)
|
|
136
|
+
} else if (CredentialMapper.isSdJwtDecodedCredential(credential)) {
|
|
137
|
+
return credential
|
|
138
|
+
} else {
|
|
139
|
+
return credential as IVerifiableCredential
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Converts a presentation to a wrapped presentation.
|
|
145
|
+
*
|
|
146
|
+
* When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
|
|
147
|
+
* an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
|
|
148
|
+
* instead of the compact SD-JWT.
|
|
149
|
+
*
|
|
150
|
+
* @param hasher Hasher implementation to use for SD-JWT decoding
|
|
151
|
+
*/
|
|
152
|
+
static toWrappedVerifiablePresentation(
|
|
153
|
+
originalPresentation: OriginalVerifiablePresentation,
|
|
154
|
+
opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
|
|
155
|
+
): WrappedVerifiablePresentation {
|
|
156
|
+
// MSO_MDOC
|
|
157
|
+
if (CredentialMapper.isMsoMdocDecodedPresentation(originalPresentation) || CredentialMapper.isMsoMdocOid4VPEncoded(originalPresentation)) {
|
|
158
|
+
let deviceResponse: MdocDeviceResponse
|
|
159
|
+
if (CredentialMapper.isMsoMdocOid4VPEncoded(originalPresentation)) {
|
|
160
|
+
deviceResponse = decodeMdocDeviceResponse(originalPresentation)
|
|
161
|
+
} else {
|
|
162
|
+
deviceResponse = originalPresentation
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const mdocCredential: WrappedMdocCredential | undefined = deviceResponse.documents
|
|
166
|
+
?.map((doc) => CredentialMapper.toWrappedVerifiableCredential(doc, opts) as WrappedMdocCredential)
|
|
167
|
+
.find((value) => value !== undefined)
|
|
168
|
+
if (mdocCredential === undefined) {
|
|
169
|
+
throw Error('mdoc credential data corruption')
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
type: CredentialMapper.isMsoMdocDecodedPresentation(originalPresentation) ? OriginalType.MSO_MDOC_DECODED : OriginalType.MSO_MDOC_ENCODED,
|
|
173
|
+
format: 'mso_mdoc',
|
|
174
|
+
original: originalPresentation,
|
|
175
|
+
presentation: deviceResponse,
|
|
176
|
+
decoded: deviceResponse,
|
|
177
|
+
vcs: [mdocCredential],
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// SD-JWT
|
|
181
|
+
if (CredentialMapper.isSdJwtDecodedCredential(originalPresentation) || CredentialMapper.isSdJwtEncoded(originalPresentation)) {
|
|
182
|
+
let decodedPresentation: SdJwtDecodedVerifiableCredential
|
|
183
|
+
if (CredentialMapper.isSdJwtEncoded(originalPresentation)) {
|
|
184
|
+
if (!opts?.hasher) {
|
|
185
|
+
throw new Error('Hasher implementation is required to decode SD-JWT')
|
|
186
|
+
}
|
|
187
|
+
decodedPresentation = decodeSdJwtVc(originalPresentation, opts.hasher)
|
|
188
|
+
} else {
|
|
189
|
+
decodedPresentation = originalPresentation
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
type: CredentialMapper.isSdJwtDecodedCredential(originalPresentation) ? OriginalType.SD_JWT_VC_DECODED : OriginalType.SD_JWT_VC_ENCODED,
|
|
193
|
+
format: 'vc+sd-jwt',
|
|
194
|
+
original: originalPresentation,
|
|
195
|
+
presentation: decodedPresentation,
|
|
196
|
+
decoded: decodedPresentation.decodedPayload,
|
|
197
|
+
// NOTE: we also include the SD-JWT VC as the VC, as the SD-JWT acts as both the VC and the VP
|
|
198
|
+
vcs: [CredentialMapper.toWrappedVerifiableCredential(originalPresentation, opts) as WrappedSdJwtVerifiableCredential],
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// If the VP is not an encoded/decoded SD-JWT, we assume it will be a W3C VC
|
|
203
|
+
const proof = CredentialMapper.getFirstProof(originalPresentation)
|
|
204
|
+
const original =
|
|
205
|
+
typeof originalPresentation !== 'string' && CredentialMapper.hasJWTProofType(originalPresentation) ? proof?.jwt : originalPresentation
|
|
206
|
+
if (!original) {
|
|
207
|
+
throw Error(
|
|
208
|
+
'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof'
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
const decoded = CredentialMapper.decodeVerifiablePresentation(original) as IVerifiablePresentation | JwtDecodedVerifiablePresentation
|
|
212
|
+
const isJwtEncoded: boolean = CredentialMapper.isJwtEncoded(original)
|
|
213
|
+
const isJwtDecoded: boolean = CredentialMapper.isJwtDecodedPresentation(original)
|
|
214
|
+
|
|
215
|
+
const type = isJwtEncoded ? OriginalType.JWT_ENCODED : isJwtDecoded ? OriginalType.JWT_DECODED : OriginalType.JSONLD
|
|
216
|
+
const format = isJwtDecoded || isJwtEncoded ? 'jwt_vp' : ('ldp_vp' as const)
|
|
217
|
+
|
|
218
|
+
let vp: OriginalVerifiablePresentation
|
|
219
|
+
if (isJwtEncoded || isJwtDecoded) {
|
|
220
|
+
vp = CredentialMapper.jwtDecodedPresentationToUniformPresentation(decoded as JwtDecodedVerifiablePresentation, false, opts)
|
|
221
|
+
} else {
|
|
222
|
+
vp = decoded as IVerifiablePresentation
|
|
223
|
+
}
|
|
224
|
+
if (!vp) {
|
|
225
|
+
throw Error(`VP key not found`)
|
|
226
|
+
}
|
|
227
|
+
const noVCs = !('verifiableCredential' in vp) || !vp.verifiableCredential || vp.verifiableCredential.length === 0
|
|
228
|
+
if (noVCs) {
|
|
229
|
+
console.warn(`Presentation without verifiable credentials. That is rare! `)
|
|
230
|
+
// throw Error(`VP needs to have at least one verifiable credential at this point`)
|
|
231
|
+
}
|
|
232
|
+
const vcs = noVCs
|
|
233
|
+
? []
|
|
234
|
+
: (CredentialMapper.toWrappedVerifiableCredentials(
|
|
235
|
+
vp.verifiableCredential ?? [] /*.map(value => value.original)*/,
|
|
236
|
+
opts
|
|
237
|
+
) as WrappedW3CVerifiableCredential[])
|
|
238
|
+
|
|
239
|
+
const presentation = {
|
|
240
|
+
...vp,
|
|
241
|
+
verifiableCredential: vcs, // We overwrite the verifiableCredentials with wrapped versions, making it an InternalVerifiablePresentation. Note: we keep the singular key name of the vc data model
|
|
242
|
+
} as UniformVerifiablePresentation
|
|
243
|
+
return {
|
|
244
|
+
type,
|
|
245
|
+
format,
|
|
246
|
+
original,
|
|
247
|
+
decoded,
|
|
248
|
+
presentation,
|
|
249
|
+
vcs,
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Converts a list of credentials to a list of wrapped credentials.
|
|
255
|
+
*
|
|
256
|
+
* When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
|
|
257
|
+
* an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
|
|
258
|
+
* instead of the compact SD-JWT.
|
|
259
|
+
*
|
|
260
|
+
* @param hasher Hasher implementation to use for SD-JWT decoding
|
|
261
|
+
*/
|
|
262
|
+
static toWrappedVerifiableCredentials(
|
|
263
|
+
verifiableCredentials: OriginalVerifiableCredential[],
|
|
264
|
+
opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
|
|
265
|
+
): WrappedVerifiableCredential[] {
|
|
266
|
+
return verifiableCredentials.map((vc) => CredentialMapper.toWrappedVerifiableCredential(vc, opts))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Converts a credential to a wrapped credential.
|
|
271
|
+
*
|
|
272
|
+
* When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
|
|
273
|
+
* an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
|
|
274
|
+
* instead of the compact SD-JWT.
|
|
275
|
+
*
|
|
276
|
+
* @param hasher Hasher implementation to use for SD-JWT decoding
|
|
277
|
+
*/
|
|
278
|
+
static toWrappedVerifiableCredential(
|
|
279
|
+
verifiableCredential: OriginalVerifiableCredential,
|
|
280
|
+
opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
|
|
281
|
+
): WrappedVerifiableCredential {
|
|
282
|
+
// MSO_MDOC
|
|
283
|
+
if (CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential) || CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential)) {
|
|
284
|
+
let mdoc: MdocDocument
|
|
285
|
+
if (CredentialMapper.isMsoMdocOid4VPEncoded(verifiableCredential)) {
|
|
286
|
+
mdoc = decodeMdocIssuerSigned(verifiableCredential)
|
|
287
|
+
} else {
|
|
288
|
+
mdoc = verifiableCredential
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
type: CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential) ? OriginalType.MSO_MDOC_DECODED : OriginalType.MSO_MDOC_ENCODED,
|
|
292
|
+
format: 'mso_mdoc',
|
|
293
|
+
original: verifiableCredential,
|
|
294
|
+
credential: mdocDecodedCredentialToUniformCredential(mdoc),
|
|
295
|
+
decoded: mdoc,
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// SD-JWT
|
|
300
|
+
if (CredentialMapper.isSdJwtDecodedCredential(verifiableCredential) || CredentialMapper.isSdJwtEncoded(verifiableCredential)) {
|
|
301
|
+
let decodedCredential: SdJwtDecodedVerifiableCredential
|
|
302
|
+
if (CredentialMapper.isSdJwtEncoded(verifiableCredential)) {
|
|
303
|
+
if (!opts?.hasher) {
|
|
304
|
+
throw new Error('Hasher implementation is required to decode SD-JWT')
|
|
305
|
+
}
|
|
306
|
+
decodedCredential = decodeSdJwtVc(verifiableCredential, opts.hasher)
|
|
307
|
+
} else {
|
|
308
|
+
decodedCredential = verifiableCredential
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
type: CredentialMapper.isSdJwtDecodedCredential(verifiableCredential) ? OriginalType.SD_JWT_VC_DECODED : OriginalType.SD_JWT_VC_ENCODED,
|
|
313
|
+
format: 'vc+sd-jwt',
|
|
314
|
+
original: verifiableCredential,
|
|
315
|
+
credential: decodedCredential,
|
|
316
|
+
decoded: decodedCredential.decodedPayload,
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// If the VC is not an encoded/decoded SD-JWT, we assume it will be a W3C VC
|
|
321
|
+
const proof = CredentialMapper.getFirstProof(verifiableCredential)
|
|
322
|
+
const original = CredentialMapper.hasJWTProofType(verifiableCredential) && proof ? proof.jwt ?? verifiableCredential : verifiableCredential
|
|
323
|
+
if (!original) {
|
|
324
|
+
throw Error(
|
|
325
|
+
'Could not determine original credential, probably it was a converted JWT credential, that is now missing the JWT value in the proof'
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
const decoded = CredentialMapper.decodeVerifiableCredential(original) as JwtDecodedVerifiableCredential | IVerifiableCredential
|
|
329
|
+
|
|
330
|
+
const isJwtEncoded = CredentialMapper.isJwtEncoded(original)
|
|
331
|
+
const isJwtDecoded = CredentialMapper.isJwtDecodedCredential(original)
|
|
332
|
+
const type = isJwtEncoded ? OriginalType.JWT_ENCODED : isJwtDecoded ? OriginalType.JWT_DECODED : OriginalType.JSONLD
|
|
333
|
+
|
|
334
|
+
const credential =
|
|
335
|
+
isJwtEncoded || isJwtDecoded
|
|
336
|
+
? CredentialMapper.jwtDecodedCredentialToUniformCredential(decoded as JwtDecodedVerifiableCredential, opts)
|
|
337
|
+
: (decoded as IVerifiableCredential)
|
|
338
|
+
|
|
339
|
+
const format = isJwtEncoded || isJwtDecoded ? ('jwt_vc' as const) : ('ldp_vc' as const)
|
|
340
|
+
return {
|
|
341
|
+
original,
|
|
342
|
+
decoded,
|
|
343
|
+
format,
|
|
344
|
+
type,
|
|
345
|
+
credential,
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
public static isJwtEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
|
|
350
|
+
return ObjectUtils.isString(original) && original.startsWith('ey') && !original.includes('~')
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
public static isSdJwtEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
|
|
354
|
+
return ObjectUtils.isString(original) && original.startsWith('ey') && original.includes('~')
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
public static isMsoMdocOid4VPEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
|
|
358
|
+
return ObjectUtils.isString(original) && !original.startsWith('ey') && ObjectUtils.isBase64(original)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
public static isW3cCredential(credential: ICredential | SdJwtDecodedVerifiableCredential): credential is ICredential {
|
|
362
|
+
return typeof credential === 'object' && '@context' in credential && ((credential as ICredential).type?.includes('VerifiableCredential') || false)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
public static isCredential(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is OriginalVerifiableCredential {
|
|
366
|
+
try {
|
|
367
|
+
if (CredentialMapper.isJwtEncoded(original)) {
|
|
368
|
+
const vc: IVerifiableCredential = CredentialMapper.toUniformCredential(original)
|
|
369
|
+
return CredentialMapper.isW3cCredential(vc)
|
|
370
|
+
} else if (CredentialMapper.isSdJwtEncoded(original)) {
|
|
371
|
+
return true
|
|
372
|
+
} else if (CredentialMapper.isMsoMdocDecodedCredential(original)) {
|
|
373
|
+
return true
|
|
374
|
+
} else if (CredentialMapper.isMsoMdocOid4VPEncoded(original)) {
|
|
375
|
+
return true
|
|
376
|
+
}
|
|
377
|
+
return (
|
|
378
|
+
CredentialMapper.isW3cCredential(original as ICredential) ||
|
|
379
|
+
CredentialMapper.isSdJwtDecodedCredentialPayload(original as ICredential) ||
|
|
380
|
+
CredentialMapper.isJwtDecodedCredential(original as OriginalVerifiableCredential) ||
|
|
381
|
+
CredentialMapper.isSdJwtDecodedCredential(original as OriginalVerifiableCredential)
|
|
382
|
+
)
|
|
383
|
+
} catch (e) {
|
|
384
|
+
return false
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
public static isPresentation(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is OriginalVerifiablePresentation {
|
|
389
|
+
try {
|
|
390
|
+
if (CredentialMapper.isJwtEncoded(original)) {
|
|
391
|
+
const vp: IVerifiablePresentation = CredentialMapper.toUniformPresentation(original)
|
|
392
|
+
return CredentialMapper.isW3cPresentation(vp)
|
|
393
|
+
} else if (CredentialMapper.isSdJwtEncoded(original)) {
|
|
394
|
+
return false
|
|
395
|
+
// @ts-expect-error
|
|
396
|
+
} else if (CredentialMapper.isMsoMdocDecodedPresentation(original)) {
|
|
397
|
+
return true
|
|
398
|
+
} else if (CredentialMapper.isMsoMdocOid4VPEncoded(original)) {
|
|
399
|
+
return true
|
|
400
|
+
}
|
|
401
|
+
return (
|
|
402
|
+
CredentialMapper.isW3cPresentation(original as IPresentation) ||
|
|
403
|
+
CredentialMapper.isSdJwtDecodedCredentialPayload(original as ICredential) ||
|
|
404
|
+
CredentialMapper.isJwtDecodedPresentation(original as OriginalVerifiablePresentation) ||
|
|
405
|
+
CredentialMapper.isSdJwtDecodedCredential(original as OriginalVerifiableCredential)
|
|
406
|
+
)
|
|
407
|
+
} catch (e) {
|
|
408
|
+
return false
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
public static hasProof(original: OriginalVerifiableCredential | OriginalVerifiablePresentation | string): boolean {
|
|
413
|
+
try {
|
|
414
|
+
if (CredentialMapper.isMsoMdocOid4VPEncoded(original)) {
|
|
415
|
+
return false
|
|
416
|
+
// @ts-ignore
|
|
417
|
+
} else if (CredentialMapper.isMsoMdocDecodedCredential(original) || CredentialMapper.isMsoMdocDecodedPresentation(original)) {
|
|
418
|
+
return true
|
|
419
|
+
} else if (CredentialMapper.isJwtEncoded(original) || CredentialMapper.isJwtDecodedCredential(original as OriginalVerifiableCredential)) {
|
|
420
|
+
return true
|
|
421
|
+
} else if (CredentialMapper.isSdJwtEncoded(original) || CredentialMapper.isSdJwtDecodedCredential(original)) {
|
|
422
|
+
//todo: we might want to revisit this
|
|
423
|
+
return true
|
|
424
|
+
}
|
|
425
|
+
if (typeof original !== 'object') {
|
|
426
|
+
return false
|
|
427
|
+
}
|
|
428
|
+
if ('vc' in (original as JwtDecodedVerifiableCredential) && (original as JwtDecodedVerifiableCredential).vc.proof) {
|
|
429
|
+
return true
|
|
430
|
+
}
|
|
431
|
+
if ('vp' in (original as JwtDecodedVerifiablePresentation) && (original as JwtDecodedVerifiablePresentation).vp.proof) {
|
|
432
|
+
return true
|
|
433
|
+
}
|
|
434
|
+
return !!(original as IVerifiableCredential | IVerifiablePresentation).proof
|
|
435
|
+
} catch (e) {
|
|
436
|
+
return false
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
public static isW3cPresentation(
|
|
441
|
+
presentation: UniformVerifiablePresentation | IPresentation | SdJwtDecodedVerifiableCredential | DeviceResponseCbor
|
|
442
|
+
): presentation is IPresentation {
|
|
443
|
+
return (
|
|
444
|
+
typeof presentation === 'object' &&
|
|
445
|
+
'@context' in presentation &&
|
|
446
|
+
((presentation as IPresentation).type?.includes('VerifiablePresentation') || false)
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
public static isSdJwtDecodedCredentialPayload(
|
|
451
|
+
credential: ICredential | SdJwtDecodedVerifiableCredentialPayload
|
|
452
|
+
): credential is SdJwtDecodedVerifiableCredentialPayload {
|
|
453
|
+
return typeof credential === 'object' && 'vct' in credential
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
public static areOriginalVerifiableCredentialsEqual(firstOriginal: OriginalVerifiableCredential, secondOriginal: OriginalVerifiableCredential) {
|
|
457
|
+
// String (e.g. encoded jwt or SD-JWT)
|
|
458
|
+
if (typeof firstOriginal === 'string' || typeof secondOriginal === 'string') {
|
|
459
|
+
return firstOriginal === secondOriginal
|
|
460
|
+
} else if (CredentialMapper.isMsoMdocDecodedCredential(firstOriginal) || CredentialMapper.isMsoMdocDecodedCredential(secondOriginal)) {
|
|
461
|
+
if (!CredentialMapper.isMsoMdocDecodedCredential(firstOriginal) || !CredentialMapper.isMsoMdocDecodedCredential(secondOriginal)) {
|
|
462
|
+
// We are doing this over here, as the rest of the logic around it would otherwise need to be adjusted substantially
|
|
463
|
+
return false
|
|
464
|
+
}
|
|
465
|
+
return firstOriginal.toJson().toJsonString() === secondOriginal.toJson().toJsonString()
|
|
466
|
+
} else if (CredentialMapper.isSdJwtDecodedCredential(firstOriginal) || CredentialMapper.isSdJwtDecodedCredential(secondOriginal)) {
|
|
467
|
+
return firstOriginal.compactSdJwtVc === secondOriginal.compactSdJwtVc
|
|
468
|
+
} else {
|
|
469
|
+
// JSON-LD or decoded JWT. (should we compare the signatures instead?)
|
|
470
|
+
return JSON.stringify(secondOriginal.proof) === JSON.stringify(firstOriginal.proof)
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
private static isJsonLdAsString(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
|
|
475
|
+
return ObjectUtils.isString(original) && original.includes('@context')
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
public static isSdJwtDecodedCredential(
|
|
479
|
+
original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation
|
|
480
|
+
): original is SdJwtDecodedVerifiableCredential {
|
|
481
|
+
return (
|
|
482
|
+
typeof original === 'object' &&
|
|
483
|
+
((<SdJwtDecodedVerifiableCredential>original).compactSdJwtVc !== undefined || (<SdJwtDecodedVerifiableCredential>original).kbJwt !== undefined)
|
|
484
|
+
)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
public static isMsoMdocDecodedCredential(
|
|
488
|
+
original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation
|
|
489
|
+
): original is MdocDocument {
|
|
490
|
+
return typeof original === 'object' && 'issuerSigned' in original && (<MdocDocument>original).issuerSigned !== undefined
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
public static isMsoMdocDecodedPresentation(original: OriginalVerifiablePresentation): original is MdocDeviceResponse {
|
|
494
|
+
return typeof original === 'object' && 'version' in original && (<MdocDeviceResponse>original).version !== undefined
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
public static isJwtDecodedCredential(original: OriginalVerifiableCredential): original is JwtDecodedVerifiableCredential {
|
|
498
|
+
return (
|
|
499
|
+
typeof original === 'object' &&
|
|
500
|
+
(<JwtDecodedVerifiableCredential>original).vc !== undefined &&
|
|
501
|
+
(<JwtDecodedVerifiableCredential>original).iss !== undefined
|
|
502
|
+
)
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
public static isJwtDecodedPresentation(original: OriginalVerifiablePresentation): original is JwtDecodedVerifiablePresentation {
|
|
506
|
+
return (
|
|
507
|
+
typeof original === 'object' &&
|
|
508
|
+
(<JwtDecodedVerifiablePresentation>original).vp !== undefined &&
|
|
509
|
+
(<JwtDecodedVerifiablePresentation>original).iss !== undefined
|
|
510
|
+
)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
public static isWrappedSdJwtVerifiableCredential = isWrappedSdJwtVerifiableCredential
|
|
514
|
+
public static isWrappedSdJwtVerifiablePresentation = isWrappedSdJwtVerifiablePresentation
|
|
515
|
+
public static isWrappedW3CVerifiableCredential = isWrappedW3CVerifiableCredential
|
|
516
|
+
public static isWrappedW3CVerifiablePresentation = isWrappedW3CVerifiablePresentation
|
|
517
|
+
public static isWrappedMdocCredential = isWrappedMdocCredential
|
|
518
|
+
public static isWrappedMdocPresentation = isWrappedMdocPresentation
|
|
519
|
+
|
|
520
|
+
static jwtEncodedPresentationToUniformPresentation(
|
|
521
|
+
jwt: string,
|
|
522
|
+
makeCredentialsUniform: boolean = true,
|
|
523
|
+
opts?: { maxTimeSkewInMS?: number }
|
|
524
|
+
): IPresentation {
|
|
525
|
+
return CredentialMapper.jwtDecodedPresentationToUniformPresentation(jwt_decode(jwt), makeCredentialsUniform, opts)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
static jwtDecodedPresentationToUniformPresentation(
|
|
529
|
+
decoded: JwtDecodedVerifiablePresentation,
|
|
530
|
+
makeCredentialsUniform: boolean = true,
|
|
531
|
+
opts?: { maxTimeSkewInMS?: number }
|
|
532
|
+
): IVerifiablePresentation {
|
|
533
|
+
const { iss, aud, jti, vp, ...rest } = decoded
|
|
534
|
+
|
|
535
|
+
const presentation: IVerifiablePresentation = {
|
|
536
|
+
...rest,
|
|
537
|
+
...vp,
|
|
538
|
+
}
|
|
539
|
+
if (makeCredentialsUniform) {
|
|
540
|
+
if (!vp.verifiableCredential) {
|
|
541
|
+
throw Error('Verifiable Presentation should have a verifiable credential at this point')
|
|
542
|
+
}
|
|
543
|
+
presentation.verifiableCredential = vp.verifiableCredential.map((vc) => CredentialMapper.toUniformCredential(vc, opts))
|
|
544
|
+
}
|
|
545
|
+
if (iss) {
|
|
546
|
+
const holder = presentation.holder
|
|
547
|
+
if (holder) {
|
|
548
|
+
if (holder !== iss) {
|
|
549
|
+
throw new Error(`Inconsistent holders between JWT claim (${iss}) and VC value (${holder})`)
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
presentation.holder = iss
|
|
553
|
+
}
|
|
554
|
+
if (aud) {
|
|
555
|
+
const verifier = presentation.verifier
|
|
556
|
+
if (verifier) {
|
|
557
|
+
if (verifier !== aud) {
|
|
558
|
+
throw new Error(`Inconsistent holders between JWT claim (${aud}) and VC value (${verifier})`)
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
presentation.verifier = aud
|
|
562
|
+
}
|
|
563
|
+
if (jti) {
|
|
564
|
+
const id = presentation.id
|
|
565
|
+
if (id && id !== jti) {
|
|
566
|
+
throw new Error(`Inconsistent VP ids between JWT claim (${jti}) and VP value (${id})`)
|
|
567
|
+
}
|
|
568
|
+
presentation.id = jti
|
|
569
|
+
}
|
|
570
|
+
return presentation
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
static toUniformCredential(
|
|
574
|
+
verifiableCredential: OriginalVerifiableCredential,
|
|
575
|
+
opts?: {
|
|
576
|
+
maxTimeSkewInMS?: number
|
|
577
|
+
hasher?: Hasher
|
|
578
|
+
}
|
|
579
|
+
): IVerifiableCredential {
|
|
580
|
+
if (CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential)) {
|
|
581
|
+
return mdocDecodedCredentialToUniformCredential(verifiableCredential)
|
|
582
|
+
}
|
|
583
|
+
if (CredentialMapper.isSdJwtDecodedCredential(verifiableCredential)) {
|
|
584
|
+
return sdJwtDecodedCredentialToUniformCredential(verifiableCredential, opts)
|
|
585
|
+
}
|
|
586
|
+
const original =
|
|
587
|
+
typeof verifiableCredential !== 'string' && CredentialMapper.hasJWTProofType(verifiableCredential)
|
|
588
|
+
? CredentialMapper.getFirstProof(verifiableCredential)?.jwt
|
|
589
|
+
: verifiableCredential
|
|
590
|
+
if (!original) {
|
|
591
|
+
throw Error(
|
|
592
|
+
'Could not determine original credential from passed in credential. Probably because a JWT proof type was present, but now is not available anymore'
|
|
593
|
+
)
|
|
594
|
+
}
|
|
595
|
+
const decoded = CredentialMapper.decodeVerifiableCredential(original, opts?.hasher)
|
|
596
|
+
|
|
597
|
+
const isJwtEncoded: boolean = CredentialMapper.isJwtEncoded(original)
|
|
598
|
+
const isJwtDecoded: boolean = CredentialMapper.isJwtDecodedCredential(original)
|
|
599
|
+
const isSdJwtEncoded = CredentialMapper.isSdJwtEncoded(original)
|
|
600
|
+
const isMdocEncoded = CredentialMapper.isMsoMdocOid4VPEncoded(original)
|
|
601
|
+
|
|
602
|
+
if (isSdJwtEncoded) {
|
|
603
|
+
return sdJwtDecodedCredentialToUniformCredential(decoded as SdJwtDecodedVerifiableCredential, opts)
|
|
604
|
+
} else if (isMdocEncoded) {
|
|
605
|
+
return mdocDecodedCredentialToUniformCredential(decodeMdocIssuerSigned(original))
|
|
606
|
+
} else if (isJwtDecoded || isJwtEncoded) {
|
|
607
|
+
return CredentialMapper.jwtDecodedCredentialToUniformCredential(decoded as JwtDecodedVerifiableCredential, opts)
|
|
608
|
+
} else {
|
|
609
|
+
return decoded as IVerifiableCredential
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
static toUniformPresentation(
|
|
614
|
+
presentation: OriginalVerifiablePresentation,
|
|
615
|
+
opts?: { maxTimeSkewInMS?: number; addContextIfMissing?: boolean }
|
|
616
|
+
): IVerifiablePresentation {
|
|
617
|
+
if (CredentialMapper.isSdJwtDecodedCredential(presentation)) {
|
|
618
|
+
throw new Error('Converting SD-JWT VC to uniform VP is not supported.')
|
|
619
|
+
} else if (CredentialMapper.isMsoMdocDecodedPresentation(presentation)) {
|
|
620
|
+
throw new Error('Converting MSO_MDOC to uniform VP is not supported yet.')
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const proof = CredentialMapper.getFirstProof(presentation)
|
|
624
|
+
const original = typeof presentation !== 'string' && CredentialMapper.hasJWTProofType(presentation) ? proof?.jwt : presentation
|
|
625
|
+
if (!original) {
|
|
626
|
+
throw Error(
|
|
627
|
+
'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof'
|
|
628
|
+
)
|
|
629
|
+
}
|
|
630
|
+
const decoded = CredentialMapper.decodeVerifiablePresentation(original)
|
|
631
|
+
const isJwtEncoded: boolean = CredentialMapper.isJwtEncoded(original)
|
|
632
|
+
const isJwtDecoded: boolean = CredentialMapper.isJwtDecodedPresentation(original)
|
|
633
|
+
const uniformPresentation =
|
|
634
|
+
isJwtEncoded || isJwtDecoded
|
|
635
|
+
? CredentialMapper.jwtDecodedPresentationToUniformPresentation(decoded as JwtDecodedVerifiablePresentation, false)
|
|
636
|
+
: (decoded as IVerifiablePresentation)
|
|
637
|
+
|
|
638
|
+
// At time of writing Velocity Networks does not conform to specification. Adding bare minimum @context section to stop parsers from crashing and whatnot
|
|
639
|
+
if (opts?.addContextIfMissing && !uniformPresentation['@context']) {
|
|
640
|
+
uniformPresentation['@context'] = ['https://www.w3.org/2018/credentials/v1']
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
uniformPresentation.verifiableCredential = uniformPresentation.verifiableCredential?.map((vc) =>
|
|
644
|
+
CredentialMapper.toUniformCredential(vc, opts)
|
|
645
|
+
) as IVerifiableCredential[] // We cast it because we IPresentation needs a VC. The internal Credential doesn't have the required Proof anymore (that is intended)
|
|
646
|
+
return uniformPresentation
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
static jwtEncodedCredentialToUniformCredential(
|
|
650
|
+
jwt: string,
|
|
651
|
+
opts?: {
|
|
652
|
+
maxTimeSkewInMS?: number
|
|
653
|
+
}
|
|
654
|
+
): IVerifiableCredential {
|
|
655
|
+
return CredentialMapper.jwtDecodedCredentialToUniformCredential(jwt_decode(jwt), opts)
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
static jwtDecodedCredentialToUniformCredential(
|
|
659
|
+
decoded: JwtDecodedVerifiableCredential,
|
|
660
|
+
opts?: { maxTimeSkewInMS?: number }
|
|
661
|
+
): IVerifiableCredential {
|
|
662
|
+
const { exp, nbf, iss, vc, sub, jti, ...rest } = decoded
|
|
663
|
+
const credential: IVerifiableCredential = {
|
|
664
|
+
...rest,
|
|
665
|
+
...vc,
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const maxSkewInMS = opts?.maxTimeSkewInMS ?? 1500
|
|
669
|
+
|
|
670
|
+
if (exp) {
|
|
671
|
+
const expDate = credential.expirationDate
|
|
672
|
+
const jwtExp = parseInt(exp.toString())
|
|
673
|
+
// fix seconds to millisecond for the date
|
|
674
|
+
const expDateAsStr = jwtExp < 9999999999 ? new Date(jwtExp * 1000).toISOString().replace(/\.000Z/, 'Z') : new Date(jwtExp).toISOString()
|
|
675
|
+
if (expDate && expDate !== expDateAsStr) {
|
|
676
|
+
const diff = Math.abs(new Date(expDateAsStr).getTime() - new Date(expDate).getTime())
|
|
677
|
+
if (!maxSkewInMS || diff > maxSkewInMS) {
|
|
678
|
+
throw new Error(`Inconsistent expiration dates between JWT claim (${expDateAsStr}) and VC value (${expDate})`)
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
credential.expirationDate = expDateAsStr
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (nbf) {
|
|
685
|
+
const issuanceDate = credential.issuanceDate
|
|
686
|
+
const jwtNbf = parseInt(nbf.toString())
|
|
687
|
+
// fix seconds to millisecs for the date
|
|
688
|
+
const nbfDateAsStr = jwtNbf < 9999999999 ? new Date(jwtNbf * 1000).toISOString().replace(/\.000Z/, 'Z') : new Date(jwtNbf).toISOString()
|
|
689
|
+
if (issuanceDate && issuanceDate !== nbfDateAsStr) {
|
|
690
|
+
const diff = Math.abs(new Date(nbfDateAsStr).getTime() - new Date(issuanceDate).getTime())
|
|
691
|
+
if (!maxSkewInMS || diff > maxSkewInMS) {
|
|
692
|
+
throw new Error(`Inconsistent issuance dates between JWT claim (${nbfDateAsStr}) and VC value (${issuanceDate})`)
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
credential.issuanceDate = nbfDateAsStr
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (iss) {
|
|
699
|
+
const issuer = credential.issuer
|
|
700
|
+
if (issuer) {
|
|
701
|
+
if (typeof issuer === 'string') {
|
|
702
|
+
if (issuer !== iss) {
|
|
703
|
+
throw new Error(`Inconsistent issuers between JWT claim (${iss}) and VC value (${issuer})`)
|
|
704
|
+
}
|
|
705
|
+
} else {
|
|
706
|
+
if (!issuer.id && Object.keys(issuer).length > 0) {
|
|
707
|
+
// We have an issuer object with more than 1 property but without an issuer id. Set it,
|
|
708
|
+
// because the default behaviour of did-jwt-vc is to remove the id value when creating JWTs
|
|
709
|
+
issuer.id = iss
|
|
710
|
+
}
|
|
711
|
+
if (issuer.id !== iss) {
|
|
712
|
+
throw new Error(`Inconsistent issuers between JWT claim (${iss}) and VC value (${issuer.id})`)
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
} else {
|
|
716
|
+
credential.issuer = iss
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (sub) {
|
|
721
|
+
const subjects = Array.isArray(credential.credentialSubject) ? credential.credentialSubject : [credential.credentialSubject]
|
|
722
|
+
for (let i = 0; i < subjects.length; i++) {
|
|
723
|
+
const csId = subjects[i].id
|
|
724
|
+
if (csId && csId !== sub) {
|
|
725
|
+
throw new Error(`Inconsistent credential subject ids between JWT claim (${sub}) and VC value (${csId})`)
|
|
726
|
+
}
|
|
727
|
+
Array.isArray(credential.credentialSubject) ? (credential.credentialSubject[i].id = sub) : (credential.credentialSubject.id = sub)
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
if (jti) {
|
|
731
|
+
const id = credential.id
|
|
732
|
+
if (id && id !== jti) {
|
|
733
|
+
throw new Error(`Inconsistent credential ids between JWT claim (${jti}) and VC value (${id})`)
|
|
734
|
+
}
|
|
735
|
+
credential.id = jti
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return credential
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
static toExternalVerifiableCredential(verifiableCredential: any): IVerifiableCredential {
|
|
742
|
+
let proof
|
|
743
|
+
if (verifiableCredential.proof) {
|
|
744
|
+
if (!verifiableCredential.proof.type) {
|
|
745
|
+
throw new Error('Verifiable credential proof is missing a type')
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
if (!verifiableCredential.proof.created) {
|
|
749
|
+
throw new Error('Verifiable credential proof is missing a created date')
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (!verifiableCredential.proof.proofPurpose) {
|
|
753
|
+
throw new Error('Verifiable credential proof is missing a proof purpose')
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (!verifiableCredential.proof.verificationMethod) {
|
|
757
|
+
throw new Error('Verifiable credential proof is missing a verification method')
|
|
758
|
+
}
|
|
759
|
+
proof = {
|
|
760
|
+
...verifiableCredential.proof,
|
|
761
|
+
type: verifiableCredential.proof.type,
|
|
762
|
+
created: verifiableCredential.proof.created,
|
|
763
|
+
proofPurpose: verifiableCredential.proof.proofPurpose,
|
|
764
|
+
verificationMethod: verifiableCredential.proof.verificationMethod,
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
return {
|
|
769
|
+
...verifiableCredential,
|
|
770
|
+
type: verifiableCredential.type
|
|
771
|
+
? typeof verifiableCredential.type === 'string'
|
|
772
|
+
? [verifiableCredential.type]
|
|
773
|
+
: verifiableCredential.type
|
|
774
|
+
: ['VerifiableCredential'],
|
|
775
|
+
proof,
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
static storedCredentialToOriginalFormat(credential: OriginalVerifiableCredential): W3CVerifiableCredential {
|
|
780
|
+
const type: DocumentFormat = CredentialMapper.detectDocumentType(credential)
|
|
781
|
+
if (typeof credential === 'string') {
|
|
782
|
+
if (type === DocumentFormat.JWT) {
|
|
783
|
+
return CredentialMapper.toCompactJWT(credential)
|
|
784
|
+
} else if (type === DocumentFormat.JSONLD) {
|
|
785
|
+
return JSON.parse(credential)
|
|
786
|
+
} else if (type === DocumentFormat.SD_JWT_VC) {
|
|
787
|
+
return credential
|
|
788
|
+
}
|
|
789
|
+
} else if (type === DocumentFormat.JWT && ObjectUtils.isObject(credential) && 'vc' in credential) {
|
|
790
|
+
return CredentialMapper.toCompactJWT(credential)
|
|
791
|
+
} else if (ObjectUtils.isObject(credential) && 'proof' in credential && credential.proof.type === 'JwtProof2020' && credential.proof.jwt) {
|
|
792
|
+
return credential.proof.jwt
|
|
793
|
+
} else if (
|
|
794
|
+
ObjectUtils.isObject(credential) &&
|
|
795
|
+
'proof' in credential &&
|
|
796
|
+
credential.proof.type === IProofType.SdJwtProof2024 &&
|
|
797
|
+
credential.proof.jwt
|
|
798
|
+
) {
|
|
799
|
+
return credential.proof.jwt
|
|
800
|
+
} else if (type === DocumentFormat.SD_JWT_VC && this.isSdJwtDecodedCredential(credential)) {
|
|
801
|
+
return credential.compactSdJwtVc
|
|
802
|
+
}
|
|
803
|
+
return credential as W3CVerifiableCredential
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
static storedPresentationToOriginalFormat(presentation: OriginalVerifiablePresentation): W3CVerifiablePresentation {
|
|
807
|
+
const type: DocumentFormat = CredentialMapper.detectDocumentType(presentation)
|
|
808
|
+
if (typeof presentation === 'string') {
|
|
809
|
+
if (type === DocumentFormat.JWT) {
|
|
810
|
+
return CredentialMapper.toCompactJWT(presentation)
|
|
811
|
+
} else if (type === DocumentFormat.JSONLD) {
|
|
812
|
+
return JSON.parse(presentation)
|
|
813
|
+
}
|
|
814
|
+
} else if (type === DocumentFormat.JWT && ObjectUtils.isObject(presentation) && 'vp' in presentation) {
|
|
815
|
+
return CredentialMapper.toCompactJWT(presentation)
|
|
816
|
+
} else if (
|
|
817
|
+
ObjectUtils.isObject(presentation) &&
|
|
818
|
+
'proof' in presentation &&
|
|
819
|
+
presentation.proof.type === 'JwtProof2020' &&
|
|
820
|
+
presentation.proof.jwt
|
|
821
|
+
) {
|
|
822
|
+
return presentation.proof.jwt
|
|
823
|
+
}
|
|
824
|
+
return presentation as W3CVerifiablePresentation
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
static toCompactJWT(
|
|
828
|
+
jwtDocument: W3CVerifiableCredential | JwtDecodedVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiablePresentation | string
|
|
829
|
+
): string {
|
|
830
|
+
if (!jwtDocument || CredentialMapper.detectDocumentType(jwtDocument) !== DocumentFormat.JWT) {
|
|
831
|
+
throw Error('Cannot convert non JWT credential to JWT')
|
|
832
|
+
}
|
|
833
|
+
if (typeof jwtDocument === 'string') {
|
|
834
|
+
return jwtDocument
|
|
835
|
+
}
|
|
836
|
+
let proof: string | undefined
|
|
837
|
+
if (ObjectUtils.isObject(jwtDocument) && 'vp' in jwtDocument) {
|
|
838
|
+
proof = 'jwt' in jwtDocument.vp.proof ? jwtDocument.vp.proof.jwt : jwtDocument.vp.proof
|
|
839
|
+
} else if (ObjectUtils.isObject(jwtDocument) && 'vc' in jwtDocument) {
|
|
840
|
+
proof = 'jwt' in jwtDocument.vc.proof ? jwtDocument.vc.proof.jwt : jwtDocument.vc.proof
|
|
841
|
+
} else {
|
|
842
|
+
proof = Array.isArray(jwtDocument.proof) ? jwtDocument.proof[0].jwt : jwtDocument.proof.jwt
|
|
843
|
+
}
|
|
844
|
+
if (!proof) {
|
|
845
|
+
throw Error(`Could not get JWT from supplied document`)
|
|
846
|
+
}
|
|
847
|
+
return proof
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
static detectDocumentType(
|
|
851
|
+
document:
|
|
852
|
+
| W3CVerifiableCredential
|
|
853
|
+
| W3CVerifiablePresentation
|
|
854
|
+
| JwtDecodedVerifiableCredential
|
|
855
|
+
| JwtDecodedVerifiablePresentation
|
|
856
|
+
| SdJwtDecodedVerifiableCredential
|
|
857
|
+
| MdocDeviceResponse
|
|
858
|
+
| MdocDocument
|
|
859
|
+
): DocumentFormat {
|
|
860
|
+
if (this.isMsoMdocOid4VPEncoded(document as any) || this.isMsoMdocDecodedCredential(document as any)) {
|
|
861
|
+
return DocumentFormat.MSO_MDOC
|
|
862
|
+
} else if (this.isMsoMdocDecodedPresentation(document as any) || this.isMsoMdocDecodedPresentation(document as any)) {
|
|
863
|
+
return DocumentFormat.MSO_MDOC
|
|
864
|
+
} else if (this.isJsonLdAsString(document)) {
|
|
865
|
+
return DocumentFormat.JSONLD
|
|
866
|
+
} else if (this.isJwtEncoded(document)) {
|
|
867
|
+
return DocumentFormat.JWT
|
|
868
|
+
} else if (this.isSdJwtEncoded(document) || this.isSdJwtDecodedCredential(document as any)) {
|
|
869
|
+
return DocumentFormat.SD_JWT_VC
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const proofs =
|
|
873
|
+
typeof document !== 'string' &&
|
|
874
|
+
('vc' in document ? document.vc.proof : 'vp' in document ? document.vp.proof : (<IVerifiableCredential>document).proof)
|
|
875
|
+
const proof: IProof = Array.isArray(proofs) ? proofs[0] : proofs
|
|
876
|
+
|
|
877
|
+
if (proof?.jwt) {
|
|
878
|
+
return DocumentFormat.JWT
|
|
879
|
+
} else if (proof?.type === 'EthereumEip712Signature2021') {
|
|
880
|
+
return DocumentFormat.EIP712
|
|
881
|
+
}
|
|
882
|
+
return DocumentFormat.JSONLD
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
private static hasJWTProofType(
|
|
886
|
+
document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation
|
|
887
|
+
): boolean {
|
|
888
|
+
if (typeof document === 'string') {
|
|
889
|
+
return false
|
|
890
|
+
}
|
|
891
|
+
return !!CredentialMapper.getFirstProof(document)?.jwt
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
private static getFirstProof(
|
|
895
|
+
document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation
|
|
896
|
+
): IProof | undefined {
|
|
897
|
+
if (!document || typeof document === 'string') {
|
|
898
|
+
return undefined
|
|
899
|
+
}
|
|
900
|
+
const proofs = 'vc' in document ? document.vc.proof : 'vp' in document ? document.vp.proof : (<IVerifiableCredential>document).proof
|
|
901
|
+
return Array.isArray(proofs) ? proofs[0] : proofs
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
static issuerCorrelationIdFromIssuerType(issuer: IssuerType): string {
|
|
905
|
+
if (issuer === undefined) {
|
|
906
|
+
throw new Error('Issuer type us undefined')
|
|
907
|
+
} else if (typeof issuer === 'string') {
|
|
908
|
+
return issuer
|
|
909
|
+
} else if (typeof issuer === 'object') {
|
|
910
|
+
if ('id' in issuer) {
|
|
911
|
+
return issuer.id
|
|
912
|
+
} else {
|
|
913
|
+
throw new Error('Encountered an invalid issuer object: missing id property')
|
|
914
|
+
}
|
|
915
|
+
} else {
|
|
916
|
+
throw new Error('Invalid issuer type')
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|