@sphereon/ssi-sdk.sd-jwt 0.34.1-next.3 → 0.34.1-next.323
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +254 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -8
- package/dist/index.d.ts +68 -8
- package/dist/index.js +252 -41
- package/dist/index.js.map +1 -1
- package/package.json +21 -20
- package/src/__tests__/{sd-jwt.test.ts → sd-jwt-vc.test.ts} +6 -4
- package/src/__tests__/sd-jwt-vcdm2.test.ts +316 -0
- package/src/action-handler.ts +86 -36
- package/src/index.ts +1 -0
- package/src/sdJwtVcdm2Instance.ts +155 -0
- package/src/types.ts +40 -6
- package/src/utils.ts +32 -1
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, it } from 'vitest'
|
|
2
|
+
import { KBJwt } from '@sd-jwt/core'
|
|
3
|
+
import { decodeSdJwt } from '@sd-jwt/decode'
|
|
4
|
+
import { SdJwtVcPayload } from '@sd-jwt/sd-jwt-vc'
|
|
5
|
+
import { DisclosureFrame, kbPayload } from '@sd-jwt/types'
|
|
6
|
+
import { JwkDIDProvider } from '@sphereon/ssi-sdk-ext.did-provider-jwk'
|
|
7
|
+
import { getDidJwkResolver } from '@sphereon/ssi-sdk-ext.did-resolver-jwk'
|
|
8
|
+
import { IdentifierResolution, IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
9
|
+
import { IJwtService, JwtService } from '@sphereon/ssi-sdk-ext.jwt-service'
|
|
10
|
+
import { MemoryKeyStore, MemoryPrivateKeyStore, SphereonKeyManager } from '@sphereon/ssi-sdk-ext.key-manager'
|
|
11
|
+
import { SphereonKeyManagementSystem } from '@sphereon/ssi-sdk-ext.kms-local'
|
|
12
|
+
import { ImDLMdoc, MDLMdoc } from '@sphereon/ssi-sdk.mdl-mdoc'
|
|
13
|
+
import { createAgent, IDIDManager, IKeyManager, IResolver, TAgent } from '@veramo/core'
|
|
14
|
+
import { DIDManager, MemoryDIDStore } from '@veramo/did-manager'
|
|
15
|
+
import { DIDResolverPlugin } from '@veramo/did-resolver'
|
|
16
|
+
import { DIDDocument, Resolver, VerificationMethod } from 'did-resolver'
|
|
17
|
+
import { defaultGenerateDigest } from '../defaultCallbacks'
|
|
18
|
+
import { ISDJwtPlugin, SDJwtPlugin } from '../index'
|
|
19
|
+
import { SdJwtVcdm2Payload } from '@sphereon/ssi-types'
|
|
20
|
+
|
|
21
|
+
type AgentType = IDIDManager & IKeyManager & IIdentifierResolution & IJwtService & IResolver & ISDJwtPlugin & ImDLMdoc
|
|
22
|
+
|
|
23
|
+
describe('Agent plugin', () => {
|
|
24
|
+
let agent: TAgent<AgentType>
|
|
25
|
+
|
|
26
|
+
let issuer: string
|
|
27
|
+
|
|
28
|
+
let holder: string
|
|
29
|
+
|
|
30
|
+
// Issuer Define the claims object with the user's information
|
|
31
|
+
const claims = {
|
|
32
|
+
sub: '',
|
|
33
|
+
credentialSubject: {
|
|
34
|
+
given_name: 'John',
|
|
35
|
+
family_name: 'Deo',
|
|
36
|
+
email: 'johndeo@example.com',
|
|
37
|
+
phone: '+1-202-555-0101',
|
|
38
|
+
address: {
|
|
39
|
+
street_address: '123 Main St',
|
|
40
|
+
locality: 'Anytown',
|
|
41
|
+
region: 'Anystate',
|
|
42
|
+
country: 'US',
|
|
43
|
+
},
|
|
44
|
+
birthdate: '1940-01-01',
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Issuer Define the disclosure frame to specify which claims can be disclosed
|
|
49
|
+
const disclosureFrame: DisclosureFrame<typeof claims> = {
|
|
50
|
+
credentialSubject: {
|
|
51
|
+
_sd: ['given_name', 'family_name', 'email', 'phone', 'address', 'birthdate'],
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
let testVcdm2SdJwtCredentialPayload: SdJwtVcdm2Payload
|
|
55
|
+
|
|
56
|
+
beforeAll(async () => {
|
|
57
|
+
agent = createAgent<AgentType>({
|
|
58
|
+
plugins: [
|
|
59
|
+
new SDJwtPlugin(),
|
|
60
|
+
new IdentifierResolution(),
|
|
61
|
+
new JwtService(),
|
|
62
|
+
new SphereonKeyManager({
|
|
63
|
+
store: new MemoryKeyStore(),
|
|
64
|
+
kms: {
|
|
65
|
+
local: new SphereonKeyManagementSystem(new MemoryPrivateKeyStore()),
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
new DIDResolverPlugin({
|
|
69
|
+
resolver: new Resolver({
|
|
70
|
+
...getDidJwkResolver(),
|
|
71
|
+
}),
|
|
72
|
+
}),
|
|
73
|
+
new DIDManager({
|
|
74
|
+
store: new MemoryDIDStore(),
|
|
75
|
+
defaultProvider: 'did:jwk',
|
|
76
|
+
providers: {
|
|
77
|
+
'did:jwk': new JwkDIDProvider({
|
|
78
|
+
defaultKms: 'local',
|
|
79
|
+
}),
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
],
|
|
83
|
+
})
|
|
84
|
+
issuer = await agent
|
|
85
|
+
.didManagerCreate({
|
|
86
|
+
kms: 'local',
|
|
87
|
+
provider: 'did:jwk',
|
|
88
|
+
alias: 'issuer',
|
|
89
|
+
//we use this curve since nodejs does not support ES256k which is the default one.
|
|
90
|
+
options: { keyType: 'Secp256r1' },
|
|
91
|
+
})
|
|
92
|
+
.then((did) => {
|
|
93
|
+
// we add a key reference
|
|
94
|
+
return `${did.did}#0`
|
|
95
|
+
})
|
|
96
|
+
holder = await agent
|
|
97
|
+
.didManagerCreate({
|
|
98
|
+
kms: 'local',
|
|
99
|
+
provider: 'did:jwk',
|
|
100
|
+
alias: 'holder',
|
|
101
|
+
//we use this curve since nodejs does not support ES256k which is the default one.
|
|
102
|
+
options: { keyType: 'Secp256r1' },
|
|
103
|
+
})
|
|
104
|
+
.then((did) => `${did.did}#0`)
|
|
105
|
+
claims.sub = holder
|
|
106
|
+
|
|
107
|
+
testVcdm2SdJwtCredentialPayload = {
|
|
108
|
+
...claims,
|
|
109
|
+
'@context': ['https://www.w3.org/ns/credentials/v2'],
|
|
110
|
+
type: ['VerifiableCredential'],
|
|
111
|
+
issuer,
|
|
112
|
+
validFrom: '2021-01-01T00:00:00Z',
|
|
113
|
+
// iat: Math.floor(new Date().getTime() / 1000),
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('create a sd-jwt vcdm2', async () => {
|
|
118
|
+
const credentialPayload: SdJwtVcdm2Payload = {
|
|
119
|
+
...claims,
|
|
120
|
+
'@context': ['https://www.w3.org/ns/credentials/v2'],
|
|
121
|
+
type: ['VerifiableCredential'],
|
|
122
|
+
issuer,
|
|
123
|
+
validFrom: '2021-01-01T00:00:00Z',
|
|
124
|
+
iat: Math.floor(new Date().getTime() / 1000),
|
|
125
|
+
}
|
|
126
|
+
const credential = await agent.createSdJwtVc({
|
|
127
|
+
credentialPayload,
|
|
128
|
+
disclosureFrame,
|
|
129
|
+
})
|
|
130
|
+
expect(credential).toBeDefined()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('create sd-jwt vcdm2 without an issuer', async () => {
|
|
134
|
+
const credentialPayload = {
|
|
135
|
+
...claims,
|
|
136
|
+
'@context': ['https://www.w3.org/ns/credentials/v2'],
|
|
137
|
+
type: ['VerifiableCredential'],
|
|
138
|
+
validFrom: '2021-01-01T00:00:00Z',
|
|
139
|
+
// iat: Math.floor(new Date().getTime() / 1000),
|
|
140
|
+
}
|
|
141
|
+
await expect(
|
|
142
|
+
agent.createSdJwtVc({
|
|
143
|
+
credentialPayload: credentialPayload as unknown as SdJwtVcPayload,
|
|
144
|
+
disclosureFrame,
|
|
145
|
+
}),
|
|
146
|
+
).rejects.toThrow('No issuer (iss or VCDM 2 issuer) found in SD-JWT or no VCDM2 SD-JWT or SD-JWT VC')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('verify a sd-jwt vcdm2', async () => {
|
|
150
|
+
const credential = await agent.createSdJwtVc({
|
|
151
|
+
credentialPayload: testVcdm2SdJwtCredentialPayload,
|
|
152
|
+
disclosureFrame: disclosureFrame,
|
|
153
|
+
})
|
|
154
|
+
const result = await agent.verifySdJwtVc({
|
|
155
|
+
credential: credential.credential,
|
|
156
|
+
})
|
|
157
|
+
expect(result.payload).toBeDefined()
|
|
158
|
+
}, 5000)
|
|
159
|
+
|
|
160
|
+
it('create a presentation', async () => {
|
|
161
|
+
const credentialPayload = testVcdm2SdJwtCredentialPayload
|
|
162
|
+
const credential = await agent.createSdJwtVc({
|
|
163
|
+
credentialPayload,
|
|
164
|
+
disclosureFrame,
|
|
165
|
+
})
|
|
166
|
+
const presentation = await agent.createSdJwtPresentation({
|
|
167
|
+
presentation: credential.credential,
|
|
168
|
+
presentationFrame: { given_name: true },
|
|
169
|
+
kb: {
|
|
170
|
+
payload: {
|
|
171
|
+
aud: '1',
|
|
172
|
+
iat: 1,
|
|
173
|
+
nonce: '342',
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
expect(presentation).toBeDefined()
|
|
178
|
+
const decoded = await decodeSdJwt(presentation.presentation, defaultGenerateDigest)
|
|
179
|
+
expect(decoded.kbJwt).toBeDefined()
|
|
180
|
+
expect(((decoded.kbJwt as KBJwt).payload as kbPayload).aud).toBe('1')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('create presentation with cnf', async () => {
|
|
184
|
+
const did = await agent.didManagerFind({ alias: 'holder' }).then((dids) => dids[0])
|
|
185
|
+
const resolvedDid = await agent.resolveDid({ didUrl: `${did.did}#0` })
|
|
186
|
+
const jwk: JsonWebKey = ((resolvedDid.didDocument as DIDDocument).verificationMethod as VerificationMethod[])[0].publicKeyJwk as JsonWebKey
|
|
187
|
+
const credentialPayload = {
|
|
188
|
+
...testVcdm2SdJwtCredentialPayload,
|
|
189
|
+
cnf: {
|
|
190
|
+
jwk,
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
const credential = await agent.createSdJwtVc({
|
|
194
|
+
credentialPayload,
|
|
195
|
+
disclosureFrame,
|
|
196
|
+
})
|
|
197
|
+
const presentation = await agent.createSdJwtPresentation({
|
|
198
|
+
presentation: credential.credential,
|
|
199
|
+
presentationFrame: { given_name: true },
|
|
200
|
+
kb: {
|
|
201
|
+
payload: {
|
|
202
|
+
aud: '1',
|
|
203
|
+
iat: 1,
|
|
204
|
+
nonce: '342',
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
expect(presentation).toBeDefined()
|
|
209
|
+
const decoded = await decodeSdJwt(presentation.presentation, defaultGenerateDigest)
|
|
210
|
+
expect(decoded.kbJwt).toBeDefined()
|
|
211
|
+
expect(((decoded.kbJwt as KBJwt).payload as kbPayload).aud).toBe('1')
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('includes no holder reference', async () => {
|
|
215
|
+
const newClaims = JSON.parse(JSON.stringify(claims))
|
|
216
|
+
newClaims.sub = undefined
|
|
217
|
+
const credentialPayload: SdJwtVcPayload = {
|
|
218
|
+
...newClaims,
|
|
219
|
+
iss: issuer,
|
|
220
|
+
iat: Math.floor(new Date().getTime() / 1000),
|
|
221
|
+
vct: '',
|
|
222
|
+
}
|
|
223
|
+
const credential = await agent.createSdJwtVc({
|
|
224
|
+
credentialPayload,
|
|
225
|
+
disclosureFrame,
|
|
226
|
+
})
|
|
227
|
+
const presentation = agent.createSdJwtPresentation({
|
|
228
|
+
presentation: credential.credential,
|
|
229
|
+
presentationFrame: { given_name: true },
|
|
230
|
+
kb: {
|
|
231
|
+
payload: {
|
|
232
|
+
aud: '1',
|
|
233
|
+
iat: 1,
|
|
234
|
+
nonce: '342',
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
await expect(presentation).rejects.toThrow('credential does not include a holder reference')
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('verify a presentation', async () => {
|
|
242
|
+
const holderDId = await agent.resolveDid({ didUrl: holder })
|
|
243
|
+
const jwk: JsonWebKey = ((holderDId.didDocument as DIDDocument).verificationMethod as VerificationMethod[])[0].publicKeyJwk as JsonWebKey
|
|
244
|
+
const credentialPayload: SdJwtVcPayload = {
|
|
245
|
+
...claims,
|
|
246
|
+
iss: issuer,
|
|
247
|
+
iat: Math.floor(new Date().getTime() / 1000),
|
|
248
|
+
vct: '',
|
|
249
|
+
cnf: {
|
|
250
|
+
jwk,
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
const credential = await agent.createSdJwtVc({
|
|
254
|
+
credentialPayload,
|
|
255
|
+
disclosureFrame,
|
|
256
|
+
})
|
|
257
|
+
const presentation = await agent.createSdJwtPresentation({
|
|
258
|
+
presentation: credential.credential,
|
|
259
|
+
presentationFrame: { credentialSubject: { given_name: true } },
|
|
260
|
+
kb: {
|
|
261
|
+
payload: {
|
|
262
|
+
aud: '1',
|
|
263
|
+
iat: 1,
|
|
264
|
+
nonce: '342',
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
})
|
|
268
|
+
const result = await agent.verifySdJwtPresentation({
|
|
269
|
+
presentation: presentation.presentation,
|
|
270
|
+
requiredClaimKeys: ['credentialSubject.given_name'],
|
|
271
|
+
// we are not able to verify the kb yet since we have no reference to the public key of the holder.
|
|
272
|
+
keyBindingAud: '1',
|
|
273
|
+
keyBindingNonce: '342',
|
|
274
|
+
})
|
|
275
|
+
expect(result).toBeDefined()
|
|
276
|
+
expect((result.payload as typeof claims).credentialSubject.given_name).toBe('John')
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('verify a presentation with sub set', async () => {
|
|
280
|
+
const holderDId = await agent.resolveDid({ didUrl: holder })
|
|
281
|
+
const jwk: JsonWebKey = ((holderDId.didDocument as DIDDocument).verificationMethod as VerificationMethod[])[0].publicKeyJwk as JsonWebKey
|
|
282
|
+
const credentialPayload: SdJwtVcPayload = {
|
|
283
|
+
...claims,
|
|
284
|
+
iss: issuer,
|
|
285
|
+
iat: Math.floor(new Date().getTime() / 1000),
|
|
286
|
+
vct: '',
|
|
287
|
+
cnf: {
|
|
288
|
+
jwk,
|
|
289
|
+
},
|
|
290
|
+
}
|
|
291
|
+
const credential = await agent.createSdJwtVc({
|
|
292
|
+
credentialPayload,
|
|
293
|
+
disclosureFrame,
|
|
294
|
+
})
|
|
295
|
+
const presentation = await agent.createSdJwtPresentation({
|
|
296
|
+
presentation: credential.credential,
|
|
297
|
+
presentationFrame: { credentialSubject: { given_name: true } },
|
|
298
|
+
kb: {
|
|
299
|
+
payload: {
|
|
300
|
+
aud: '1',
|
|
301
|
+
iat: 1,
|
|
302
|
+
nonce: '342',
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
})
|
|
306
|
+
const result = await agent.verifySdJwtPresentation({
|
|
307
|
+
presentation: presentation.presentation,
|
|
308
|
+
requiredClaimKeys: ['credentialSubject.given_name'],
|
|
309
|
+
// we are not able to verify the kb yet since we have no reference to the public key of the holder.
|
|
310
|
+
keyBindingAud: '1',
|
|
311
|
+
keyBindingNonce: '342',
|
|
312
|
+
})
|
|
313
|
+
expect(result).toBeDefined()
|
|
314
|
+
expect((result.payload as typeof claims).credentialSubject.given_name).toBe('John')
|
|
315
|
+
})
|
|
316
|
+
})
|
package/src/action-handler.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
import { Jwt, SDJwt } from '@sd-jwt/core'
|
|
2
|
-
import { SDJwtVcInstance, SdJwtVcPayload } from '@sd-jwt/sd-jwt-vc'
|
|
3
|
-
import { DisclosureFrame, Hasher, JwtPayload, KbVerifier, PresentationFrame, Signer, Verifier } from '@sd-jwt/types'
|
|
1
|
+
import { Jwt, SDJwt, type SdJwtPayload, type VerifierOptions } from '@sd-jwt/core'
|
|
2
|
+
import { SDJwtVcInstance, type SdJwtVcPayload } from '@sd-jwt/sd-jwt-vc'
|
|
3
|
+
import type { DisclosureFrame, HashAlgorithm, Hasher, JwtPayload, KbVerifier, PresentationFrame, Signer, Verifier } from '@sd-jwt/types'
|
|
4
4
|
import { calculateJwkThumbprint, signatureAlgorithmFromKey } from '@sphereon/ssi-sdk-ext.key-utils'
|
|
5
|
-
import { X509CertificateChainValidationOpts } from '@sphereon/ssi-sdk-ext.x509-utils'
|
|
6
|
-
import { HasherSync, JsonWebKey, JWK, SdJwtTypeMetadata } from '@sphereon/ssi-types'
|
|
7
|
-
import { IAgentPlugin } from '@veramo/core'
|
|
8
|
-
import { decodeBase64url } from '@veramo/utils'
|
|
5
|
+
import type { X509CertificateChainValidationOpts } from '@sphereon/ssi-sdk-ext.x509-utils'
|
|
6
|
+
import type { HasherSync, JsonWebKey, JWK, SdJwtTypeMetadata } from '@sphereon/ssi-types'
|
|
7
|
+
import type { IAgentPlugin } from '@veramo/core'
|
|
8
|
+
// import { decodeBase64url } from '@veramo/utils'
|
|
9
9
|
import Debug from 'debug'
|
|
10
10
|
import { defaultGenerateDigest, defaultGenerateSalt, defaultVerifySignature } from './defaultCallbacks'
|
|
11
11
|
import { funkeTestCA, sphereonCA } from './trustAnchors'
|
|
12
|
-
import { assertValidTypeMetadata, fetchUrlWithErrorHandling, validateIntegrity } from './utils'
|
|
13
12
|
import {
|
|
13
|
+
assertValidTypeMetadata,
|
|
14
|
+
fetchUrlWithErrorHandling,
|
|
15
|
+
getIssuerFromSdJwt,
|
|
16
|
+
isSdjwtVcPayload,
|
|
17
|
+
isVcdm2SdJwtPayload,
|
|
18
|
+
validateIntegrity,
|
|
19
|
+
} from './utils'
|
|
20
|
+
import type {
|
|
14
21
|
Claims,
|
|
15
22
|
FetchSdJwtTypeMetadataFromVctUrlArgs,
|
|
16
23
|
GetSignerForIdentifierArgs,
|
|
@@ -30,6 +37,10 @@ import {
|
|
|
30
37
|
SignKeyArgs,
|
|
31
38
|
SignKeyResult,
|
|
32
39
|
} from './types'
|
|
40
|
+
import { SDJwtVcdm2Instance, SDJwtVcdmInstanceFactory } from './sdJwtVcdm2Instance'
|
|
41
|
+
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
import * as u8a from 'uint8arrays'
|
|
33
44
|
|
|
34
45
|
const debug = Debug('@sphereon/ssi-sdk.sd-jwt')
|
|
35
46
|
|
|
@@ -101,27 +112,47 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
101
112
|
* @returns A signed SD-JWT credential.
|
|
102
113
|
*/
|
|
103
114
|
async createSdJwtVc(args: ICreateSdJwtVcArgs, context: IRequiredContext): Promise<ICreateSdJwtVcResult> {
|
|
104
|
-
const
|
|
115
|
+
const payload = args.credentialPayload
|
|
116
|
+
const isVcdm2 = isVcdm2SdJwtPayload(payload)
|
|
117
|
+
const isSdJwtVc = isSdjwtVcPayload(payload)
|
|
118
|
+
const type = args.type ?? (isVcdm2 ? 'vc+sd-jwt' : 'dc+sd-jwt')
|
|
119
|
+
|
|
120
|
+
const issuer = getIssuerFromSdJwt(args.credentialPayload)
|
|
105
121
|
if (!issuer) {
|
|
106
122
|
throw new Error('credential.issuer must not be empty')
|
|
107
123
|
}
|
|
108
124
|
const { alg, signer, signingKey } = await this.getSignerForIdentifier({ identifier: issuer, resolution: args.resolution }, context)
|
|
109
|
-
const
|
|
125
|
+
const signAlg = alg ?? signingKey?.alg ?? 'ES256'
|
|
126
|
+
const hashAlg: HashAlgorithm = /(\d{3})$/.test(signAlg) ? (`sha-${signAlg.slice(-3)}` as HashAlgorithm) : 'sha-256'
|
|
127
|
+
const sdjwt = SDJwtVcdmInstanceFactory.create(type, {
|
|
128
|
+
omitTyp: true,
|
|
110
129
|
signer,
|
|
111
130
|
hasher: this.registeredImplementations.hasher,
|
|
112
131
|
saltGenerator: this.registeredImplementations.saltGenerator,
|
|
113
|
-
signAlg
|
|
114
|
-
hashAlg
|
|
132
|
+
signAlg,
|
|
133
|
+
hashAlg,
|
|
115
134
|
})
|
|
116
135
|
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
136
|
+
const header = {
|
|
137
|
+
...(signingKey?.key.kid !== undefined && { kid: signingKey.key.kid }),
|
|
138
|
+
...(signingKey?.key.x5c !== undefined && { x5c: signingKey.key.x5c }),
|
|
139
|
+
...(type && { typ: type }),
|
|
140
|
+
}
|
|
141
|
+
let credential: string
|
|
142
|
+
if (isVcdm2) {
|
|
143
|
+
credential = await (sdjwt as SDJwtVcdm2Instance).issue(
|
|
144
|
+
payload,
|
|
145
|
+
// @ts-ignore
|
|
146
|
+
args.disclosureFrame as DisclosureFrame<typeof payload>,
|
|
147
|
+
{ header },
|
|
148
|
+
)
|
|
149
|
+
} else if (isSdJwtVc) {
|
|
150
|
+
credential = await (sdjwt as SDJwtVcInstance).issue(payload, args.disclosureFrame as DisclosureFrame<typeof payload>, { header })
|
|
151
|
+
} else {
|
|
152
|
+
return Promise.reject(new Error(`invalid_argument: credential '${type}' type is not supported`))
|
|
153
|
+
}
|
|
123
154
|
|
|
124
|
-
return { credential }
|
|
155
|
+
return { type, credential }
|
|
125
156
|
}
|
|
126
157
|
|
|
127
158
|
/**
|
|
@@ -183,10 +214,13 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
183
214
|
* @returns A signed SD-JWT presentation.
|
|
184
215
|
*/
|
|
185
216
|
async createSdJwtPresentation(args: ICreateSdJwtPresentationArgs, context: IRequiredContext): Promise<ICreateSdJwtPresentationResult> {
|
|
217
|
+
const type = args.type ?? 'dc+sd-jwt'
|
|
218
|
+
|
|
186
219
|
const cred = await SDJwt.fromEncode(args.presentation, this.registeredImplementations.hasher!)
|
|
220
|
+
|
|
187
221
|
const claims = await cred.getClaims<Claims>(this.registeredImplementations.hasher!)
|
|
188
222
|
let holder: string
|
|
189
|
-
// we
|
|
223
|
+
// we primarily look for a cnf field, if it's not there, we look for a sub field. If this is also not given, we throw an error since we can not sign it.
|
|
190
224
|
if (args.holder) {
|
|
191
225
|
holder = args.holder
|
|
192
226
|
} else if (claims.cnf?.jwk) {
|
|
@@ -201,15 +235,17 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
201
235
|
}
|
|
202
236
|
const { alg, signer } = await this.getSignerForIdentifier({ identifier: holder }, context)
|
|
203
237
|
|
|
204
|
-
const sdjwt =
|
|
205
|
-
|
|
238
|
+
const sdjwt = SDJwtVcdmInstanceFactory.create(type, {
|
|
239
|
+
omitTyp: true,
|
|
240
|
+
hasher: this.registeredImplementations.hasher,
|
|
206
241
|
saltGenerator: this.registeredImplementations.saltGenerator,
|
|
207
242
|
kbSigner: signer,
|
|
208
243
|
kbSignAlg: alg ?? 'ES256',
|
|
209
244
|
})
|
|
245
|
+
|
|
210
246
|
const presentation = await sdjwt.present(args.presentation, args.presentationFrame as PresentationFrame<SdJwtVcPayload>, { kb: args.kb })
|
|
211
247
|
|
|
212
|
-
return { presentation }
|
|
248
|
+
return { type, presentation }
|
|
213
249
|
}
|
|
214
250
|
|
|
215
251
|
/**
|
|
@@ -220,11 +256,17 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
220
256
|
*/
|
|
221
257
|
async verifySdJwtVc(args: IVerifySdJwtVcArgs, context: IRequiredContext): Promise<IVerifySdJwtVcResult> {
|
|
222
258
|
// callback
|
|
223
|
-
const verifier: Verifier = async (data: string, signature: string) => this.
|
|
224
|
-
|
|
225
|
-
const
|
|
259
|
+
const verifier: Verifier = async (data: string, signature: string) => this.verifyCallbackImpl(sdjwt, context, data, signature)
|
|
260
|
+
|
|
261
|
+
const cred = await SDJwt.fromEncode(args.credential, this.registeredImplementations.hasher!)
|
|
262
|
+
const type = isVcdm2SdJwtPayload(cred.jwt?.payload as SdJwtPayload) ? 'vc+sd-jwt' : 'dc+sd-jwt'
|
|
263
|
+
|
|
264
|
+
const sdjwt = SDJwtVcdmInstanceFactory.create(type, { verifier, hasher: this.registeredImplementations.hasher ?? defaultGenerateDigest })
|
|
265
|
+
// FIXME: Findynet. Issuer returns expired status lists, and low level lib throws errors on these. We need to fix this in our implementation by wrapping the verification function
|
|
266
|
+
// For now a workaround is to ad 5 days of skew seconds, yuck
|
|
267
|
+
const { header = {}, payload, kb } = await sdjwt.verify(args.credential, { skewSeconds: 60 * 60 * 24 * 5 })
|
|
226
268
|
|
|
227
|
-
return { header, payload
|
|
269
|
+
return { type, header, payload, kb }
|
|
228
270
|
}
|
|
229
271
|
|
|
230
272
|
/**
|
|
@@ -236,10 +278,13 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
236
278
|
* @param payload - The payload of the SD-JWT
|
|
237
279
|
* @returns
|
|
238
280
|
*/
|
|
239
|
-
private verifyKb(
|
|
281
|
+
private verifyKb(context: IRequiredContext, data: string, signature: string, payload: JwtPayload): Promise<boolean> {
|
|
240
282
|
if (!payload.cnf) {
|
|
241
283
|
throw Error('other method than cnf is not supported yet')
|
|
242
284
|
}
|
|
285
|
+
|
|
286
|
+
// TODO add aud verification
|
|
287
|
+
|
|
243
288
|
return this.verifySignatureCallback(context)(data, signature, this.getJwk(payload))
|
|
244
289
|
}
|
|
245
290
|
|
|
@@ -251,15 +296,16 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
251
296
|
* @param signature - The signature
|
|
252
297
|
* @returns
|
|
253
298
|
*/
|
|
254
|
-
async
|
|
255
|
-
sdjwt: SDJwtVcInstance,
|
|
299
|
+
async verifyCallbackImpl(
|
|
300
|
+
sdjwt: SDJwtVcInstance | SDJwtVcdm2Instance,
|
|
256
301
|
context: IRequiredContext,
|
|
257
302
|
data: string,
|
|
258
303
|
signature: string,
|
|
259
304
|
opts?: { x5cValidation?: X509CertificateChainValidationOpts },
|
|
260
305
|
): Promise<boolean> {
|
|
261
306
|
const decodedVC = await sdjwt.decode(`${data}.${signature}`)
|
|
262
|
-
const
|
|
307
|
+
const payload: SdJwtPayload = (decodedVC.jwt as Jwt).payload as SdJwtPayload
|
|
308
|
+
const issuer: string = getIssuerFromSdJwt(payload)
|
|
263
309
|
const header = (decodedVC.jwt as Jwt).header as Record<string, any>
|
|
264
310
|
const x5c: string[] | undefined = header?.x5c as string[]
|
|
265
311
|
let jwk: JWK | JsonWebKey | undefined = header.jwk
|
|
@@ -329,16 +375,20 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
329
375
|
*/
|
|
330
376
|
async verifySdJwtPresentation(args: IVerifySdJwtPresentationArgs, context: IRequiredContext): Promise<IVerifySdJwtPresentationResult> {
|
|
331
377
|
let sdjwt: SDJwtVcInstance
|
|
332
|
-
const verifier: Verifier = async (data: string, signature: string) => this.
|
|
333
|
-
const verifierKb: KbVerifier = async (data: string, signature: string, payload: JwtPayload) =>
|
|
334
|
-
this.verifyKb(sdjwt, context, data, signature, payload)
|
|
378
|
+
const verifier: Verifier = async (data: string, signature: string) => this.verifyCallbackImpl(sdjwt, context, data, signature)
|
|
379
|
+
const verifierKb: KbVerifier = async (data: string, signature: string, payload: JwtPayload) => this.verifyKb(context, data, signature, payload)
|
|
335
380
|
sdjwt = new SDJwtVcInstance({
|
|
336
381
|
verifier,
|
|
337
382
|
hasher: this.registeredImplementations.hasher,
|
|
338
383
|
kbVerifier: verifierKb,
|
|
339
384
|
})
|
|
340
385
|
|
|
341
|
-
|
|
386
|
+
const verifierOpts: VerifierOptions = {
|
|
387
|
+
requiredClaimKeys: args.requiredClaimKeys,
|
|
388
|
+
keyBindingNonce: args.keyBindingNonce,
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return sdjwt.verify(args.presentation, verifierOpts)
|
|
342
392
|
}
|
|
343
393
|
|
|
344
394
|
/**
|
|
@@ -411,7 +461,7 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
411
461
|
// extract JWK from kid FIXME isn't there a did function for this already? Otherwise create one
|
|
412
462
|
// FIXME this is a quick-fix to make verification but we need a real solution
|
|
413
463
|
const encoded = this.extractBase64FromDIDJwk(payload.cnf.kid)
|
|
414
|
-
const decoded =
|
|
464
|
+
const decoded = u8a.toString(u8a.fromString(encoded, 'base64url'), 'utf-8')
|
|
415
465
|
const jwt = JSON.parse(decoded)
|
|
416
466
|
return jwt as JsonWebKey
|
|
417
467
|
}
|
package/src/index.ts
CHANGED