@sphereon/ssi-sdk.sd-jwt 0.34.1-next.6 → 0.34.1-next.85
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 +251 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -8
- package/dist/index.d.ts +66 -8
- package/dist/index.js +249 -40
- 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 +80 -35
- package/src/sdJwtVcdm2Instance.ts +155 -0
- package/src/types.ts +40 -6
- package/src/utils.ts +32 -1
package/dist/index.js
CHANGED
|
@@ -3,9 +3,8 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
3
3
|
|
|
4
4
|
// src/action-handler.ts
|
|
5
5
|
import { SDJwt } from "@sd-jwt/core";
|
|
6
|
-
import { SDJwtVcInstance } from "@sd-jwt/sd-jwt-vc";
|
|
6
|
+
import { SDJwtVcInstance as SDJwtVcInstance2 } from "@sd-jwt/sd-jwt-vc";
|
|
7
7
|
import { calculateJwkThumbprint, signatureAlgorithmFromKey } from "@sphereon/ssi-sdk-ext.key-utils";
|
|
8
|
-
import { decodeBase64url } from "@veramo/utils";
|
|
9
8
|
import Debug from "debug";
|
|
10
9
|
|
|
11
10
|
// src/defaultCallbacks.ts
|
|
@@ -81,8 +80,190 @@ function assertValidTypeMetadata(metadata, vct) {
|
|
|
81
80
|
}
|
|
82
81
|
}
|
|
83
82
|
__name(assertValidTypeMetadata, "assertValidTypeMetadata");
|
|
83
|
+
function isVcdm2SdJwtPayload(payload) {
|
|
84
|
+
return "type" in payload && Array.isArray(payload.type) && payload.type.includes("VerifiableCredential") && "@context" in payload && (typeof payload["@context"] === "string" && payload["@context"].length > 0 || Array.isArray(payload["@context"]) && payload["@context"].length > 0 && payload["@context"].includes("https://www.w3.org/ns/credentials/v2"));
|
|
85
|
+
}
|
|
86
|
+
__name(isVcdm2SdJwtPayload, "isVcdm2SdJwtPayload");
|
|
87
|
+
function isSdjwtVcPayload(payload) {
|
|
88
|
+
return !isVcdm2SdJwtPayload(payload) && "vct" in payload && typeof payload.vct === "string";
|
|
89
|
+
}
|
|
90
|
+
__name(isSdjwtVcPayload, "isSdjwtVcPayload");
|
|
91
|
+
function getIssuerFromSdJwt(payload) {
|
|
92
|
+
let issuer;
|
|
93
|
+
if (isVcdm2SdJwtPayload(payload)) {
|
|
94
|
+
issuer = typeof payload.issuer === "string" ? payload.issuer : payload.issuer?.id;
|
|
95
|
+
}
|
|
96
|
+
if (isSdjwtVcPayload(payload)) {
|
|
97
|
+
issuer = payload.iss;
|
|
98
|
+
}
|
|
99
|
+
if (!issuer) {
|
|
100
|
+
throw new Error("No issuer (iss or VCDM 2 issuer) found in SD-JWT or no VCDM2 SD-JWT or SD-JWT VC");
|
|
101
|
+
}
|
|
102
|
+
return issuer;
|
|
103
|
+
}
|
|
104
|
+
__name(getIssuerFromSdJwt, "getIssuerFromSdJwt");
|
|
105
|
+
|
|
106
|
+
// src/sdJwtVcdm2Instance.ts
|
|
107
|
+
import { SDJwtInstance } from "@sd-jwt/core";
|
|
108
|
+
import { SDJWTException } from "@sd-jwt/utils";
|
|
109
|
+
import { SDJwtVcInstance } from "@sd-jwt/sd-jwt-vc";
|
|
110
|
+
|
|
111
|
+
// src/types.ts
|
|
112
|
+
import { contextHasPlugin } from "@sphereon/ssi-sdk.agent-config";
|
|
113
|
+
var sdJwtPluginContextMethods = [
|
|
114
|
+
"createSdJwtVc",
|
|
115
|
+
"createSdJwtPresentation",
|
|
116
|
+
"verifySdJwtVc",
|
|
117
|
+
"verifySdJwtPresentation"
|
|
118
|
+
];
|
|
119
|
+
function contextHasSDJwtPlugin(context) {
|
|
120
|
+
return contextHasPlugin(context, "verifySdJwtVc");
|
|
121
|
+
}
|
|
122
|
+
__name(contextHasSDJwtPlugin, "contextHasSDJwtPlugin");
|
|
123
|
+
function isVcdm2SdJwt(type) {
|
|
124
|
+
return type === "vc+sd-jwt" || type === "vp+sd-jwt";
|
|
125
|
+
}
|
|
126
|
+
__name(isVcdm2SdJwt, "isVcdm2SdJwt");
|
|
127
|
+
|
|
128
|
+
// src/sdJwtVcdm2Instance.ts
|
|
129
|
+
var SDJwtVcdmInstanceFactory = class {
|
|
130
|
+
static {
|
|
131
|
+
__name(this, "SDJwtVcdmInstanceFactory");
|
|
132
|
+
}
|
|
133
|
+
static create(type, config) {
|
|
134
|
+
if (isVcdm2SdJwt(type)) {
|
|
135
|
+
return new SDJwtVcdm2Instance(config);
|
|
136
|
+
}
|
|
137
|
+
return new SDJwtVcInstance(config);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
var SDJwtVcdm2Instance = class extends SDJwtInstance {
|
|
141
|
+
static {
|
|
142
|
+
__name(this, "SDJwtVcdm2Instance");
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The type of the SD-JWT VCDM2 set in the header.typ field.
|
|
146
|
+
*/
|
|
147
|
+
static type = "vc+sd-jwt";
|
|
148
|
+
userConfig = {};
|
|
149
|
+
constructor(userConfig) {
|
|
150
|
+
super(userConfig);
|
|
151
|
+
if (userConfig) {
|
|
152
|
+
this.userConfig = userConfig;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Validates if the disclosureFrame contains any reserved fields. If so it will throw an error.
|
|
157
|
+
* @param disclosureFrame
|
|
158
|
+
*/
|
|
159
|
+
validateReservedFields(disclosureFrame) {
|
|
160
|
+
if (disclosureFrame?._sd && Array.isArray(disclosureFrame._sd) && disclosureFrame._sd.length > 0) {
|
|
161
|
+
const reservedNames = [
|
|
162
|
+
"iss",
|
|
163
|
+
"nbf",
|
|
164
|
+
"exp",
|
|
165
|
+
"cnf",
|
|
166
|
+
"@context",
|
|
167
|
+
"type",
|
|
168
|
+
"credentialStatus",
|
|
169
|
+
"credentialSchema",
|
|
170
|
+
"relatedResource"
|
|
171
|
+
];
|
|
172
|
+
const reservedNamesInDisclosureFrame = disclosureFrame._sd.filter((key) => reservedNames.includes(key));
|
|
173
|
+
if (reservedNamesInDisclosureFrame.length > 0) {
|
|
174
|
+
throw new SDJWTException(`Cannot disclose protected field(s): ${reservedNamesInDisclosureFrame.join(", ")}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Verifies the SD-JWT-VC. It will validate the signature, the keybindings when required, the status, and the VCT.
|
|
180
|
+
* @param encodedSDJwt
|
|
181
|
+
* @param options
|
|
182
|
+
*/
|
|
183
|
+
async verify(encodedSDJwt, options) {
|
|
184
|
+
const result = await super.verify(encodedSDJwt, options).then((res) => {
|
|
185
|
+
return {
|
|
186
|
+
payload: res.payload,
|
|
187
|
+
header: res.header,
|
|
188
|
+
kb: res.kb
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Validates the integrity of the response if the integrity is passed. If the integrity does not match, an error is thrown.
|
|
195
|
+
* @param integrity
|
|
196
|
+
* @param response
|
|
197
|
+
*/
|
|
198
|
+
async validateIntegrity(response, url, integrity) {
|
|
199
|
+
if (integrity) {
|
|
200
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
201
|
+
const alg = integrity.split("-")[0];
|
|
202
|
+
const hashBuffer = await this.userConfig.hasher(arrayBuffer, alg);
|
|
203
|
+
const integrityHash = integrity.split("-")[1];
|
|
204
|
+
const hash = Array.from(new Uint8Array(hashBuffer)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
205
|
+
if (hash !== integrityHash) {
|
|
206
|
+
throw new Error(`Integrity check for ${url} failed: is ${hash}, but expected ${integrityHash}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Fetches the content from the url with a timeout of 10 seconds.
|
|
212
|
+
* @param url
|
|
213
|
+
* @param integrity
|
|
214
|
+
* @returns
|
|
215
|
+
*/
|
|
216
|
+
async fetch(url, integrity) {
|
|
217
|
+
try {
|
|
218
|
+
const response = await fetch(url, {
|
|
219
|
+
signal: AbortSignal.timeout(this.userConfig.timeout ?? 1e4)
|
|
220
|
+
});
|
|
221
|
+
if (!response.ok) {
|
|
222
|
+
const errorText = await response.text();
|
|
223
|
+
return Promise.reject(new Error(`Error fetching ${url}: ${response.status} ${response.statusText} - ${errorText}`));
|
|
224
|
+
}
|
|
225
|
+
await this.validateIntegrity(response.clone(), url, integrity);
|
|
226
|
+
return response.json();
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (error.name === "TimeoutError") {
|
|
229
|
+
throw new Error(`Request to ${url} timed out`);
|
|
230
|
+
}
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async issue(payload, disclosureFrame, options) {
|
|
235
|
+
if (payload.iss && !payload.issuer) {
|
|
236
|
+
payload.issuer = {
|
|
237
|
+
id: payload.iss
|
|
238
|
+
};
|
|
239
|
+
delete payload.iss;
|
|
240
|
+
}
|
|
241
|
+
if (payload.nbf && !payload.validFrom) {
|
|
242
|
+
payload.validFrom = toVcdm2Date(payload.nbf);
|
|
243
|
+
delete payload.nbf;
|
|
244
|
+
}
|
|
245
|
+
if (payload.exp && !payload.validUntil) {
|
|
246
|
+
payload.validUntil = toVcdm2Date(payload.exp);
|
|
247
|
+
delete payload.exp;
|
|
248
|
+
}
|
|
249
|
+
if (payload.sub && !Array.isArray(payload.credentialSubject) && !payload.credentialSubject.id) {
|
|
250
|
+
payload.credentialSubject.id = payload.sub;
|
|
251
|
+
delete payload.sub;
|
|
252
|
+
}
|
|
253
|
+
return super.issue(payload, disclosureFrame, options);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
function toVcdm2Date(value) {
|
|
257
|
+
const num = typeof value === "string" ? Number(value) : value;
|
|
258
|
+
if (!Number.isFinite(num)) {
|
|
259
|
+
throw new SDJWTException(`Invalid numeric date: ${value}`);
|
|
260
|
+
}
|
|
261
|
+
return new Date(num * 1e3).toISOString();
|
|
262
|
+
}
|
|
263
|
+
__name(toVcdm2Date, "toVcdm2Date");
|
|
84
264
|
|
|
85
265
|
// src/action-handler.ts
|
|
266
|
+
import * as u8a from "uint8arrays";
|
|
86
267
|
var debug = Debug("@sphereon/ssi-sdk.sd-jwt");
|
|
87
268
|
var SDJwtPlugin = class {
|
|
88
269
|
static {
|
|
@@ -152,7 +333,11 @@ var SDJwtPlugin = class {
|
|
|
152
333
|
* @returns A signed SD-JWT credential.
|
|
153
334
|
*/
|
|
154
335
|
async createSdJwtVc(args, context) {
|
|
155
|
-
const
|
|
336
|
+
const payload = args.credentialPayload;
|
|
337
|
+
const isVcdm2 = isVcdm2SdJwtPayload(payload);
|
|
338
|
+
const isSdJwtVc = isSdjwtVcPayload(payload);
|
|
339
|
+
const type = args.type ?? (isVcdm2 ? "vc+sd-jwt" : "dc+sd-jwt");
|
|
340
|
+
const issuer = getIssuerFromSdJwt(args.credentialPayload);
|
|
156
341
|
if (!issuer) {
|
|
157
342
|
throw new Error("credential.issuer must not be empty");
|
|
158
343
|
}
|
|
@@ -160,24 +345,46 @@ var SDJwtPlugin = class {
|
|
|
160
345
|
identifier: issuer,
|
|
161
346
|
resolution: args.resolution
|
|
162
347
|
}, context);
|
|
163
|
-
const
|
|
348
|
+
const signAlg = alg ?? signingKey?.alg ?? "ES256";
|
|
349
|
+
const hashAlg = /(\d{3})$/.test(signAlg) ? `sha-${signAlg.slice(-3)}` : "sha-256";
|
|
350
|
+
const sdjwt = SDJwtVcdmInstanceFactory.create(type, {
|
|
351
|
+
omitTyp: true,
|
|
164
352
|
signer,
|
|
165
353
|
hasher: this.registeredImplementations.hasher,
|
|
166
354
|
saltGenerator: this.registeredImplementations.saltGenerator,
|
|
167
|
-
signAlg
|
|
168
|
-
hashAlg
|
|
355
|
+
signAlg,
|
|
356
|
+
hashAlg
|
|
169
357
|
});
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
358
|
+
const header = {
|
|
359
|
+
...signingKey?.key.kid !== void 0 && {
|
|
360
|
+
kid: signingKey.key.kid
|
|
361
|
+
},
|
|
362
|
+
...signingKey?.key.x5c !== void 0 && {
|
|
363
|
+
x5c: signingKey.key.x5c
|
|
364
|
+
},
|
|
365
|
+
...type && {
|
|
366
|
+
typ: type
|
|
178
367
|
}
|
|
179
|
-
}
|
|
368
|
+
};
|
|
369
|
+
let credential;
|
|
370
|
+
if (isVcdm2) {
|
|
371
|
+
credential = await sdjwt.issue(
|
|
372
|
+
payload,
|
|
373
|
+
// @ts-ignore
|
|
374
|
+
args.disclosureFrame,
|
|
375
|
+
{
|
|
376
|
+
header
|
|
377
|
+
}
|
|
378
|
+
);
|
|
379
|
+
} else if (isSdJwtVc) {
|
|
380
|
+
credential = await sdjwt.issue(payload, args.disclosureFrame, {
|
|
381
|
+
header
|
|
382
|
+
});
|
|
383
|
+
} else {
|
|
384
|
+
return Promise.reject(new Error(`invalid_argument: credential '${type}' type is not supported`));
|
|
385
|
+
}
|
|
180
386
|
return {
|
|
387
|
+
type,
|
|
181
388
|
credential
|
|
182
389
|
};
|
|
183
390
|
}
|
|
@@ -303,6 +510,7 @@ var SDJwtPlugin = class {
|
|
|
303
510
|
* @returns A signed SD-JWT presentation.
|
|
304
511
|
*/
|
|
305
512
|
async createSdJwtPresentation(args, context) {
|
|
513
|
+
const type = args.type ?? "dc+sd-jwt";
|
|
306
514
|
const cred = await SDJwt.fromEncode(args.presentation, this.registeredImplementations.hasher);
|
|
307
515
|
const claims = await cred.getClaims(this.registeredImplementations.hasher);
|
|
308
516
|
let holder;
|
|
@@ -323,8 +531,9 @@ var SDJwtPlugin = class {
|
|
|
323
531
|
const { alg, signer } = await this.getSignerForIdentifier({
|
|
324
532
|
identifier: holder
|
|
325
533
|
}, context);
|
|
326
|
-
const sdjwt =
|
|
327
|
-
|
|
534
|
+
const sdjwt = SDJwtVcdmInstanceFactory.create(type, {
|
|
535
|
+
omitTyp: true,
|
|
536
|
+
hasher: this.registeredImplementations.hasher,
|
|
328
537
|
saltGenerator: this.registeredImplementations.saltGenerator,
|
|
329
538
|
kbSigner: signer,
|
|
330
539
|
kbSignAlg: alg ?? "ES256"
|
|
@@ -333,6 +542,7 @@ var SDJwtPlugin = class {
|
|
|
333
542
|
kb: args.kb
|
|
334
543
|
});
|
|
335
544
|
return {
|
|
545
|
+
type,
|
|
336
546
|
presentation
|
|
337
547
|
};
|
|
338
548
|
}
|
|
@@ -343,13 +553,16 @@ var SDJwtPlugin = class {
|
|
|
343
553
|
* @returns
|
|
344
554
|
*/
|
|
345
555
|
async verifySdJwtVc(args, context) {
|
|
346
|
-
const verifier = /* @__PURE__ */ __name(async (data, signature) => this.
|
|
347
|
-
const
|
|
556
|
+
const verifier = /* @__PURE__ */ __name(async (data, signature) => this.verifyCallbackImpl(sdjwt, context, data, signature), "verifier");
|
|
557
|
+
const cred = await SDJwt.fromEncode(args.credential, this.registeredImplementations.hasher);
|
|
558
|
+
const type = isVcdm2SdJwtPayload(cred.jwt?.payload) ? "vc+sd-jwt" : "dc+sd-jwt";
|
|
559
|
+
const sdjwt = SDJwtVcdmInstanceFactory.create(type, {
|
|
348
560
|
verifier,
|
|
349
561
|
hasher: this.registeredImplementations.hasher ?? defaultGenerateDigest
|
|
350
562
|
});
|
|
351
563
|
const { header = {}, payload, kb } = await sdjwt.verify(args.credential);
|
|
352
564
|
return {
|
|
565
|
+
type,
|
|
353
566
|
header,
|
|
354
567
|
payload,
|
|
355
568
|
kb
|
|
@@ -364,7 +577,7 @@ var SDJwtPlugin = class {
|
|
|
364
577
|
* @param payload - The payload of the SD-JWT
|
|
365
578
|
* @returns
|
|
366
579
|
*/
|
|
367
|
-
verifyKb(
|
|
580
|
+
verifyKb(context, data, signature, payload) {
|
|
368
581
|
if (!payload.cnf) {
|
|
369
582
|
throw Error("other method than cnf is not supported yet");
|
|
370
583
|
}
|
|
@@ -378,9 +591,10 @@ var SDJwtPlugin = class {
|
|
|
378
591
|
* @param signature - The signature
|
|
379
592
|
* @returns
|
|
380
593
|
*/
|
|
381
|
-
async
|
|
594
|
+
async verifyCallbackImpl(sdjwt, context, data, signature, opts) {
|
|
382
595
|
const decodedVC = await sdjwt.decode(`${data}.${signature}`);
|
|
383
|
-
const
|
|
596
|
+
const payload = decodedVC.jwt.payload;
|
|
597
|
+
const issuer = getIssuerFromSdJwt(payload);
|
|
384
598
|
const header = decodedVC.jwt.header;
|
|
385
599
|
const x5c = header?.x5c;
|
|
386
600
|
let jwk = header.jwk;
|
|
@@ -446,14 +660,18 @@ var SDJwtPlugin = class {
|
|
|
446
660
|
*/
|
|
447
661
|
async verifySdJwtPresentation(args, context) {
|
|
448
662
|
let sdjwt;
|
|
449
|
-
const verifier = /* @__PURE__ */ __name(async (data, signature) => this.
|
|
450
|
-
const verifierKb = /* @__PURE__ */ __name(async (data, signature, payload) => this.verifyKb(
|
|
451
|
-
sdjwt = new
|
|
663
|
+
const verifier = /* @__PURE__ */ __name(async (data, signature) => this.verifyCallbackImpl(sdjwt, context, data, signature), "verifier");
|
|
664
|
+
const verifierKb = /* @__PURE__ */ __name(async (data, signature, payload) => this.verifyKb(context, data, signature, payload), "verifierKb");
|
|
665
|
+
sdjwt = new SDJwtVcInstance2({
|
|
452
666
|
verifier,
|
|
453
667
|
hasher: this.registeredImplementations.hasher,
|
|
454
668
|
kbVerifier: verifierKb
|
|
455
669
|
});
|
|
456
|
-
|
|
670
|
+
const verifierOpts = {
|
|
671
|
+
requiredClaimKeys: args.requiredClaimKeys,
|
|
672
|
+
keyBindingNonce: args.keyBindingNonce
|
|
673
|
+
};
|
|
674
|
+
return sdjwt.verify(args.presentation, verifierOpts);
|
|
457
675
|
}
|
|
458
676
|
/**
|
|
459
677
|
* Fetch and validate Type Metadata.
|
|
@@ -524,7 +742,7 @@ var SDJwtPlugin = class {
|
|
|
524
742
|
return payload.cnf.jwk;
|
|
525
743
|
} else if (payload.cnf !== void 0 && "kid" in payload.cnf && typeof payload.cnf.kid === "string" && payload.cnf.kid.startsWith("did:jwk:")) {
|
|
526
744
|
const encoded = this.extractBase64FromDIDJwk(payload.cnf.kid);
|
|
527
|
-
const decoded =
|
|
745
|
+
const decoded = u8a.toString(u8a.fromString(encoded, "base64url"), "utf-8");
|
|
528
746
|
const jwt = JSON.parse(decoded);
|
|
529
747
|
return jwt;
|
|
530
748
|
}
|
|
@@ -538,19 +756,6 @@ var SDJwtPlugin = class {
|
|
|
538
756
|
return parts[2].split("#")[0];
|
|
539
757
|
}
|
|
540
758
|
};
|
|
541
|
-
|
|
542
|
-
// src/types.ts
|
|
543
|
-
import { contextHasPlugin } from "@sphereon/ssi-sdk.agent-config";
|
|
544
|
-
var sdJwtPluginContextMethods = [
|
|
545
|
-
"createSdJwtVc",
|
|
546
|
-
"createSdJwtPresentation",
|
|
547
|
-
"verifySdJwtVc",
|
|
548
|
-
"verifySdJwtPresentation"
|
|
549
|
-
];
|
|
550
|
-
function contextHasSDJwtPlugin(context) {
|
|
551
|
-
return contextHasPlugin(context, "verifySdJwtVc");
|
|
552
|
-
}
|
|
553
|
-
__name(contextHasSDJwtPlugin, "contextHasSDJwtPlugin");
|
|
554
759
|
export {
|
|
555
760
|
SDJwtPlugin,
|
|
556
761
|
assertValidTypeMetadata,
|
|
@@ -558,6 +763,10 @@ export {
|
|
|
558
763
|
createIntegrity,
|
|
559
764
|
extractHashFromIntegrity,
|
|
560
765
|
fetchUrlWithErrorHandling,
|
|
766
|
+
getIssuerFromSdJwt,
|
|
767
|
+
isSdjwtVcPayload,
|
|
768
|
+
isVcdm2SdJwt,
|
|
769
|
+
isVcdm2SdJwtPayload,
|
|
561
770
|
sdJwtPluginContextMethods,
|
|
562
771
|
validateIntegrity
|
|
563
772
|
};
|