@sphereon/ssi-sdk.sd-jwt 0.34.1-feature.SSISDK.45.94 → 0.34.1-feature.SSISDK.46.41
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 +43 -253
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -66
- package/dist/index.d.ts +8 -66
- package/dist/index.js +41 -251
- package/dist/index.js.map +1 -1
- package/package.json +20 -21
- package/src/__tests__/{sd-jwt-vc.test.ts → sd-jwt.test.ts} +4 -6
- package/src/action-handler.ts +36 -83
- package/src/types.ts +6 -40
- package/src/utils.ts +1 -32
- package/src/__tests__/sd-jwt-vcdm2.test.ts +0 -316
- package/src/sdJwtVcdm2Instance.ts +0 -155
package/src/types.ts
CHANGED
|
@@ -1,22 +1,12 @@
|
|
|
1
|
+
import { SdJwtVcPayload as SdJwtPayload } from '@sd-jwt/sd-jwt-vc'
|
|
1
2
|
import { Hasher, kbHeader, KBOptions, kbPayload, SaltGenerator, Signer } from '@sd-jwt/types'
|
|
2
3
|
import { IIdentifierResolution, ManagedIdentifierResult } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
3
4
|
import { IJwtService } from '@sphereon/ssi-sdk-ext.jwt-service'
|
|
4
5
|
import { X509CertificateChainValidationOpts } from '@sphereon/ssi-sdk-ext.x509-utils'
|
|
5
6
|
import { contextHasPlugin } from '@sphereon/ssi-sdk.agent-config'
|
|
6
7
|
import { ImDLMdoc } from '@sphereon/ssi-sdk.mdl-mdoc'
|
|
7
|
-
import {
|
|
8
|
-
HasherSync,
|
|
9
|
-
JoseSignatureAlgorithm,
|
|
10
|
-
JsonWebKey,
|
|
11
|
-
SdJwtType,
|
|
12
|
-
SdJwtTypeMetadata,
|
|
13
|
-
SdJwtVcdm2Payload,
|
|
14
|
-
SdJwtVcType,
|
|
15
|
-
SdJwtVpType,
|
|
16
|
-
} from '@sphereon/ssi-types'
|
|
8
|
+
import { HasherSync, JoseSignatureAlgorithm, JsonWebKey, SdJwtTypeMetadata } from '@sphereon/ssi-types'
|
|
17
9
|
import { DIDDocumentSection, IAgentContext, IDIDManager, IKeyManager, IPluginMethodMap, IResolver } from '@veramo/core'
|
|
18
|
-
import { SdJwtVcPayload as OrigSdJwtVcPayload } from '@sd-jwt/sd-jwt-vc'
|
|
19
|
-
import { SdJwtPayload } from '@sd-jwt/core'
|
|
20
10
|
|
|
21
11
|
export const sdJwtPluginContextMethods: Array<string> = ['createSdJwtVc', 'createSdJwtPresentation', 'verifySdJwtVc', 'verifySdJwtPresentation']
|
|
22
12
|
|
|
@@ -95,19 +85,12 @@ export function contextHasSDJwtPlugin(context: IAgentContext<IPluginMethodMap>):
|
|
|
95
85
|
* @beta
|
|
96
86
|
*/
|
|
97
87
|
|
|
98
|
-
export interface SdJwtVcPayload extends
|
|
88
|
+
export interface SdJwtVcPayload extends SdJwtPayload {
|
|
99
89
|
x5c?: string[]
|
|
100
90
|
}
|
|
101
91
|
|
|
102
|
-
export type Vcdm2Enveloped = 'EnvelopedVerifiableCredential' | 'EnvelopedVerifiablePresentation'
|
|
103
|
-
|
|
104
|
-
export function isVcdm2SdJwt(type: SdJwtType | string): Boolean {
|
|
105
|
-
return type === 'vc+sd-jwt' || type === 'vp+sd-jwt'
|
|
106
|
-
}
|
|
107
|
-
|
|
108
92
|
export interface ICreateSdJwtVcArgs {
|
|
109
|
-
|
|
110
|
-
credentialPayload: SdJwtPayload
|
|
93
|
+
credentialPayload: SdJwtVcPayload
|
|
111
94
|
|
|
112
95
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
113
96
|
disclosureFrame?: IDisclosureFrame
|
|
@@ -131,8 +114,6 @@ export interface IDisclosureFrame {
|
|
|
131
114
|
* @beta
|
|
132
115
|
*/
|
|
133
116
|
export interface ICreateSdJwtVcResult {
|
|
134
|
-
type: SdJwtVcType
|
|
135
|
-
|
|
136
117
|
/**
|
|
137
118
|
* the encoded sd-jwt credential
|
|
138
119
|
*/
|
|
@@ -165,10 +146,6 @@ export interface ICreateSdJwtPresentationArgs {
|
|
|
165
146
|
* Information to include to add key binding.
|
|
166
147
|
*/
|
|
167
148
|
kb?: KBOptions
|
|
168
|
-
|
|
169
|
-
type?: SdJwtVpType
|
|
170
|
-
|
|
171
|
-
vcdm2Enveloped?: Vcdm2Enveloped
|
|
172
149
|
}
|
|
173
150
|
|
|
174
151
|
/**
|
|
@@ -187,8 +164,6 @@ export interface ICreateSdJwtPresentationResult {
|
|
|
187
164
|
* Encoded presentation.
|
|
188
165
|
*/
|
|
189
166
|
presentation: string
|
|
190
|
-
|
|
191
|
-
type: SdJwtVpType
|
|
192
167
|
}
|
|
193
168
|
|
|
194
169
|
/**
|
|
@@ -205,8 +180,7 @@ export interface IVerifySdJwtVcArgs {
|
|
|
205
180
|
* @beta
|
|
206
181
|
*/
|
|
207
182
|
export type IVerifySdJwtVcResult = {
|
|
208
|
-
|
|
209
|
-
payload: SdJwtVcPayload | SdJwtVcdm2Payload
|
|
183
|
+
payload: SdJwtPayload
|
|
210
184
|
header: Record<string, unknown>
|
|
211
185
|
kb?: { header: kbHeader; payload: kbPayload }
|
|
212
186
|
}
|
|
@@ -219,15 +193,7 @@ export interface IVerifySdJwtPresentationArgs {
|
|
|
219
193
|
|
|
220
194
|
requiredClaimKeys?: string[]
|
|
221
195
|
|
|
222
|
-
|
|
223
|
-
* nonce used to verify the key binding jwt to prevent replay attacks.
|
|
224
|
-
*/
|
|
225
|
-
keyBindingNonce?: string
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Audience used to verify the key binding jwt
|
|
229
|
-
*/
|
|
230
|
-
keyBindingAud?: string
|
|
196
|
+
kb?: boolean
|
|
231
197
|
}
|
|
232
198
|
|
|
233
199
|
/**
|
package/src/utils.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { SdJwtTypeMetadata } from '@sphereon/ssi-types'
|
|
2
2
|
// @ts-ignore
|
|
3
3
|
import { toString } from 'uint8arrays/to-string'
|
|
4
4
|
import { Hasher, HasherSync } from '@sd-jwt/types'
|
|
5
|
-
import type { SdJwtPayload } from '@sd-jwt/core'
|
|
6
|
-
import type { SdJwtVcPayload } from '@sd-jwt/sd-jwt-vc'
|
|
7
5
|
|
|
8
6
|
// Helper function to fetch API with error handling
|
|
9
7
|
export async function fetchUrlWithErrorHandling(url: string): Promise<Response> {
|
|
@@ -66,32 +64,3 @@ export function assertValidTypeMetadata(metadata: SdJwtTypeMetadata, vct: string
|
|
|
66
64
|
throw new Error('VCT mismatch in metadata and credential')
|
|
67
65
|
}
|
|
68
66
|
}
|
|
69
|
-
|
|
70
|
-
export function isVcdm2SdJwtPayload(payload: SdJwtPayload): payload is SdJwtVcdm2Payload {
|
|
71
|
-
return (
|
|
72
|
-
'type' in payload &&
|
|
73
|
-
Array.isArray(payload.type) &&
|
|
74
|
-
payload.type.includes('VerifiableCredential') &&
|
|
75
|
-
'@context' in payload &&
|
|
76
|
-
((typeof payload['@context'] === 'string' && payload['@context'].length > 0) ||
|
|
77
|
-
(Array.isArray(payload['@context']) && payload['@context'].length > 0 && payload['@context'].includes('https://www.w3.org/ns/credentials/v2')))
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function isSdjwtVcPayload(payload: SdJwtPayload): payload is SdJwtVcPayload {
|
|
82
|
-
return !isVcdm2SdJwtPayload(payload) && 'vct' in payload && typeof payload.vct === 'string'
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function getIssuerFromSdJwt(payload: SdJwtPayload): string {
|
|
86
|
-
let issuer: string | undefined
|
|
87
|
-
if (isSdjwtVcPayload(payload) || 'iss' in payload) {
|
|
88
|
-
issuer = payload.iss as string
|
|
89
|
-
} else if (isVcdm2SdJwtPayload(payload) || 'issuer' in payload && payload.issuer) {
|
|
90
|
-
issuer = typeof payload.issuer === 'string' ? payload.issuer : (payload.issuer as any)?.id
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (!issuer) {
|
|
94
|
-
throw new Error('No issuer (iss or VCDM 2 issuer) found in SD-JWT or no VCDM2 SD-JWT or SD-JWT VC')
|
|
95
|
-
}
|
|
96
|
-
return issuer
|
|
97
|
-
}
|
|
@@ -1,316 +0,0 @@
|
|
|
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
|
-
})
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { SDJwtInstance, type VerifierOptions } from '@sd-jwt/core'
|
|
2
|
-
import type { DisclosureFrame, Hasher, SDJWTCompact } from '@sd-jwt/types'
|
|
3
|
-
import { SDJWTException } from '@sd-jwt/utils'
|
|
4
|
-
import { type SdJwtType, type SDJWTVCDM2Config, type SdJwtVcdm2Payload } from '@sphereon/ssi-types'
|
|
5
|
-
import { type SDJWTVCConfig, SDJwtVcInstance, type VerificationResult } from '@sd-jwt/sd-jwt-vc'
|
|
6
|
-
import { isVcdm2SdJwt } from './types'
|
|
7
|
-
|
|
8
|
-
interface SdJwtVcdm2VerificationResult extends Omit<VerificationResult, 'payload'> {
|
|
9
|
-
payload: SdJwtVcdm2Payload
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class SDJwtVcdmInstanceFactory {
|
|
13
|
-
static create(type: SdJwtType, config: SDJWTVCConfig | SDJWTVCDM2Config): SDJwtVcdm2Instance | SDJwtVcInstance {
|
|
14
|
-
if (isVcdm2SdJwt(type)) {
|
|
15
|
-
return new SDJwtVcdm2Instance(config as SDJWTVCDM2Config)
|
|
16
|
-
}
|
|
17
|
-
return new SDJwtVcInstance(config as SDJWTVCConfig)
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// @ts-ignore
|
|
22
|
-
export class SDJwtVcdm2Instance extends SDJwtInstance<SdJwtVcdm2Payload> {
|
|
23
|
-
/**
|
|
24
|
-
* The type of the SD-JWT VCDM2 set in the header.typ field.
|
|
25
|
-
*/
|
|
26
|
-
protected static type = 'vc+sd-jwt'
|
|
27
|
-
|
|
28
|
-
protected userConfig: SDJWTVCDM2Config = {}
|
|
29
|
-
|
|
30
|
-
constructor(userConfig?: SDJWTVCDM2Config) {
|
|
31
|
-
super(userConfig)
|
|
32
|
-
if (userConfig) {
|
|
33
|
-
this.userConfig = userConfig
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Validates if the disclosureFrame contains any reserved fields. If so it will throw an error.
|
|
39
|
-
* @param disclosureFrame
|
|
40
|
-
*/
|
|
41
|
-
protected validateReservedFields(disclosureFrame: DisclosureFrame<SdJwtVcdm2Payload>): void {
|
|
42
|
-
//validate disclosureFrame according to https://www.ietf.org/archive/id/draft-ietf-oauth-sd-jwt-vc-08.html#section-3.2.2.2
|
|
43
|
-
// @ts-ignore
|
|
44
|
-
if (disclosureFrame?._sd && Array.isArray(disclosureFrame._sd) && disclosureFrame._sd.length > 0) {
|
|
45
|
-
const reservedNames = ['iss', 'nbf', 'exp', 'cnf', '@context', 'type', 'credentialStatus', 'credentialSchema', 'relatedResource']
|
|
46
|
-
// check if there is any reserved names in the disclosureFrame._sd array
|
|
47
|
-
const reservedNamesInDisclosureFrame = (disclosureFrame._sd as string[]).filter((key) => reservedNames.includes(key))
|
|
48
|
-
if (reservedNamesInDisclosureFrame.length > 0) {
|
|
49
|
-
throw new SDJWTException(`Cannot disclose protected field(s): ${reservedNamesInDisclosureFrame.join(', ')}`)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Verifies the SD-JWT-VC. It will validate the signature, the keybindings when required, the status, and the VCT.
|
|
56
|
-
* @param encodedSDJwt
|
|
57
|
-
* @param options
|
|
58
|
-
*/
|
|
59
|
-
async verify(encodedSDJwt: string, options?: VerifierOptions) {
|
|
60
|
-
// Call the parent class's verify method
|
|
61
|
-
const result: SdJwtVcdm2VerificationResult = await super.verify(encodedSDJwt, options).then((res) => {
|
|
62
|
-
return {
|
|
63
|
-
payload: res.payload as SdJwtVcdm2Payload,
|
|
64
|
-
header: res.header,
|
|
65
|
-
kb: res.kb,
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
// await this.verifyStatus(result, options)
|
|
70
|
-
|
|
71
|
-
return result
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Validates the integrity of the response if the integrity is passed. If the integrity does not match, an error is thrown.
|
|
76
|
-
* @param integrity
|
|
77
|
-
* @param response
|
|
78
|
-
*/
|
|
79
|
-
private async validateIntegrity(response: Response, url: string, integrity?: string) {
|
|
80
|
-
if (integrity) {
|
|
81
|
-
// validate the integrity of the response according to https://www.w3.org/TR/SRI/
|
|
82
|
-
const arrayBuffer = await response.arrayBuffer()
|
|
83
|
-
const alg = integrity.split('-')[0]
|
|
84
|
-
//TODO: error handling when a hasher is passed that is not supporting the required algorithm acording to the spec
|
|
85
|
-
const hashBuffer = await (this.userConfig.hasher as Hasher)(arrayBuffer, alg)
|
|
86
|
-
const integrityHash = integrity.split('-')[1]
|
|
87
|
-
const hash = Array.from(new Uint8Array(hashBuffer))
|
|
88
|
-
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
89
|
-
.join('')
|
|
90
|
-
if (hash !== integrityHash) {
|
|
91
|
-
throw new Error(`Integrity check for ${url} failed: is ${hash}, but expected ${integrityHash}`)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Fetches the content from the url with a timeout of 10 seconds.
|
|
98
|
-
* @param url
|
|
99
|
-
* @param integrity
|
|
100
|
-
* @returns
|
|
101
|
-
*/
|
|
102
|
-
protected async fetch<T>(url: string, integrity?: string): Promise<T> {
|
|
103
|
-
try {
|
|
104
|
-
const response = await fetch(url, {
|
|
105
|
-
signal: AbortSignal.timeout(this.userConfig.timeout ?? 10000),
|
|
106
|
-
})
|
|
107
|
-
if (!response.ok) {
|
|
108
|
-
const errorText = await response.text()
|
|
109
|
-
return Promise.reject(new Error(`Error fetching ${url}: ${response.status} ${response.statusText} - ${errorText}`))
|
|
110
|
-
}
|
|
111
|
-
await this.validateIntegrity(response.clone(), url, integrity)
|
|
112
|
-
return response.json() as Promise<T>
|
|
113
|
-
} catch (error) {
|
|
114
|
-
if ((error as Error).name === 'TimeoutError') {
|
|
115
|
-
throw new Error(`Request to ${url} timed out`)
|
|
116
|
-
}
|
|
117
|
-
throw error
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
public async issue<Payload extends SdJwtVcdm2Payload>(
|
|
122
|
-
payload: Payload,
|
|
123
|
-
disclosureFrame?: DisclosureFrame<Payload>,
|
|
124
|
-
options?: {
|
|
125
|
-
header?: object; // This is for customizing the header of the jwt
|
|
126
|
-
},
|
|
127
|
-
): Promise<SDJWTCompact> {
|
|
128
|
-
if (payload.iss && !payload.issuer) {
|
|
129
|
-
payload.issuer = {id: payload.iss}
|
|
130
|
-
delete payload.iss
|
|
131
|
-
}
|
|
132
|
-
if (payload.nbf && !payload.validFrom) {
|
|
133
|
-
payload.validFrom = toVcdm2Date(payload.nbf)
|
|
134
|
-
delete payload.nbf
|
|
135
|
-
}
|
|
136
|
-
if (payload.exp && !payload.validUntil) {
|
|
137
|
-
payload.validUntil = toVcdm2Date(payload.exp)
|
|
138
|
-
delete payload.exp
|
|
139
|
-
}
|
|
140
|
-
if (payload.sub && !Array.isArray(payload.credentialSubject) && !payload.credentialSubject.id) {
|
|
141
|
-
payload.credentialSubject.id = payload.sub
|
|
142
|
-
delete payload.sub
|
|
143
|
-
}
|
|
144
|
-
return super.issue(payload, disclosureFrame, options)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function toVcdm2Date(value: number | string): string {
|
|
149
|
-
const num = typeof value === 'string' ? Number(value) : value
|
|
150
|
-
if (!Number.isFinite(num)) {
|
|
151
|
-
throw new SDJWTException(`Invalid numeric date: ${value}`)
|
|
152
|
-
}
|
|
153
|
-
// Convert JWT NumericDate (seconds since epoch) to W3C VCDM 2 date-time string (RFC 3339 / ISO 8601)
|
|
154
|
-
return new Date(num * 1000).toISOString()
|
|
155
|
-
}
|