@sd-jwt/sd-jwt-vc 0.17.2-next.1 → 0.17.2-next.11

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.mjs CHANGED
@@ -1,5 +1,21 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
1
3
  var __getProtoOf = Object.getPrototypeOf;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
2
6
  var __reflectGet = Reflect.get;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
3
19
  var __superGet = (cls, obj, key) => __reflectGet(__getProtoOf(cls), key, obj);
4
20
  var __async = (__this, __arguments, generator) => {
5
21
  return new Promise((resolve, reject) => {
@@ -28,7 +44,110 @@ import {
28
44
  getListFromStatusListJWT,
29
45
  SLException
30
46
  } from "@sd-jwt/jwt-status-list";
31
- import { base64urlDecode, SDJWTException } from "@sd-jwt/utils";
47
+ import { SDJWTException } from "@sd-jwt/utils";
48
+ import z2 from "zod";
49
+
50
+ // src/sd-jwt-vc-type-metadata-format.ts
51
+ import { z } from "zod";
52
+ var LogoSchema = z.object({
53
+ /** REQUIRED. A URI pointing to the logo image. */
54
+ uri: z.string(),
55
+ /** OPTIONAL. An "integrity metadata" string as described in Section 7. */
56
+ "uri#integrity": z.string().optional(),
57
+ /** OPTIONAL. A string containing alternative text for the logo image. */
58
+ alt_text: z.string().optional()
59
+ });
60
+ var SimpleRenderingSchema = z.object({
61
+ /** OPTIONAL. Logo metadata to display for the credential. */
62
+ logo: LogoSchema.optional(),
63
+ /** OPTIONAL. RGB color value for the credential background (e.g., "#FFFFFF"). */
64
+ background_color: z.string().optional(),
65
+ /** OPTIONAL. RGB color value for the credential text (e.g., "#000000"). */
66
+ text_color: z.string().optional()
67
+ });
68
+ var OrientationSchema = z.enum(["portrait", "landscape"]);
69
+ var ColorSchemeSchema = z.enum(["light", "dark"]);
70
+ var ContrastSchema = z.enum(["normal", "high"]);
71
+ var SvgTemplatePropertiesSchema = z.object({
72
+ /** OPTIONAL. Orientation optimized for the template. */
73
+ orientation: OrientationSchema.optional(),
74
+ /** OPTIONAL. Color scheme optimized for the template. */
75
+ color_scheme: ColorSchemeSchema.optional(),
76
+ /** OPTIONAL. Contrast level optimized for the template. */
77
+ contrast: ContrastSchema.optional()
78
+ });
79
+ var SvgTemplateRenderingSchema = z.object({
80
+ /** REQUIRED. A URI pointing to the SVG template. */
81
+ uri: z.string(),
82
+ /** OPTIONAL. An "integrity metadata" string as described in Section 7. */
83
+ "uri#integrity": z.string().optional(),
84
+ /** REQUIRED if more than one SVG template is present. */
85
+ properties: SvgTemplatePropertiesSchema.optional()
86
+ });
87
+ var RenderingSchema = z.object({
88
+ /** OPTIONAL. Simple rendering metadata. */
89
+ simple: SimpleRenderingSchema.optional(),
90
+ /** OPTIONAL. Array of SVG template rendering objects. */
91
+ svg_template: z.array(SvgTemplateRenderingSchema).optional()
92
+ });
93
+ var DisplaySchema = z.object({
94
+ /** REQUIRED. Language tag according to RFC 5646 (e.g., "en", "de"). */
95
+ lang: z.string(),
96
+ /** REQUIRED. Human-readable name for the credential type. */
97
+ name: z.string(),
98
+ /** OPTIONAL. Description of the credential type for end users. */
99
+ description: z.string().optional(),
100
+ /** OPTIONAL. Rendering information (simple or SVG) for the credential. */
101
+ rendering: RenderingSchema.optional()
102
+ });
103
+ var ClaimPathSchema = z.array(z.string().nullable());
104
+ var ClaimDisplaySchema = z.object({
105
+ /** REQUIRED. Language tag according to RFC 5646. */
106
+ lang: z.string(),
107
+ /** REQUIRED. Human-readable label for the claim. */
108
+ label: z.string(),
109
+ /** OPTIONAL. Description of the claim for end users. */
110
+ description: z.string().optional()
111
+ });
112
+ var ClaimSelectiveDisclosureSchema = z.enum([
113
+ "always",
114
+ "allowed",
115
+ "never"
116
+ ]);
117
+ var ClaimSchema = z.object({
118
+ /**
119
+ * REQUIRED. Array of one or more paths to the claim in the credential subject.
120
+ * Each path is an array of strings (or null for array elements).
121
+ */
122
+ path: ClaimPathSchema,
123
+ /** OPTIONAL. Display metadata in multiple languages. */
124
+ display: z.array(ClaimDisplaySchema).optional(),
125
+ /** OPTIONAL. Controls whether the claim must, may, or must not be selectively disclosed. */
126
+ sd: ClaimSelectiveDisclosureSchema.optional(),
127
+ /**
128
+ * OPTIONAL. Unique string identifier for referencing the claim in an SVG template.
129
+ * Must consist of alphanumeric characters or underscores and must not start with a digit.
130
+ */
131
+ svg_id: z.string().optional()
132
+ });
133
+ var TypeMetadataFormatSchema = z.object({
134
+ /** REQUIRED. A URI uniquely identifying the credential type. */
135
+ vct: z.string(),
136
+ /** OPTIONAL. Human-readable name for developers. */
137
+ name: z.string().optional(),
138
+ /** OPTIONAL. Human-readable description for developers. */
139
+ description: z.string().optional(),
140
+ /** OPTIONAL. URI of another type that this one extends. */
141
+ extends: z.string().optional(),
142
+ /** OPTIONAL. Integrity metadata for the 'extends' field. */
143
+ "extends#integrity": z.string().optional(),
144
+ /** OPTIONAL. Array of localized display metadata for the type. */
145
+ display: z.array(DisplaySchema).optional(),
146
+ /** OPTIONAL. Array of claim metadata. */
147
+ claims: z.array(ClaimSchema).optional()
148
+ });
149
+
150
+ // src/sd-jwt-vc-instance.ts
32
151
  var SDJwtVcInstance = class _SDJwtVcInstance extends SDJwtInstance {
33
152
  constructor(userConfig) {
34
153
  super(userConfig);
@@ -109,13 +228,17 @@ var SDJwtVcInstance = class _SDJwtVcInstance extends SDJwtInstance {
109
228
  });
110
229
  yield this.verifyStatus(result, options);
111
230
  if (this.userConfig.loadTypeMetadataFormat) {
112
- yield this.verifyVct(result);
231
+ const resolvedTypeMetadata = yield this.fetchVct(result);
232
+ result.typeMetadata = resolvedTypeMetadata;
113
233
  }
114
234
  return result;
115
235
  });
116
236
  }
117
237
  /**
118
238
  * Gets VCT Metadata of the raw SD-JWT-VC. Returns the type metadata format. If the SD-JWT-VC is invalid or does not contain a vct claim, an error is thrown.
239
+ *
240
+ * It may return `undefined` if the fetcher returned an undefined value (instead of throwing an error).
241
+ *
119
242
  * @param encodedSDJwt
120
243
  * @returns
121
244
  */
@@ -140,20 +263,19 @@ var SDJwtVcInstance = class _SDJwtVcInstance extends SDJwtInstance {
140
263
  */
141
264
  validateIntegrity(response, url, integrity) {
142
265
  return __async(this, null, function* () {
143
- if (integrity) {
144
- const arrayBuffer = yield response.arrayBuffer();
145
- const alg = integrity.split("-")[0];
146
- const hashBuffer = yield this.userConfig.hasher(
147
- arrayBuffer,
148
- alg
266
+ if (!integrity) return;
267
+ const arrayBuffer = yield response.arrayBuffer();
268
+ const alg = integrity.split("-")[0];
269
+ const hashBuffer = yield this.userConfig.hasher(
270
+ arrayBuffer,
271
+ alg
272
+ );
273
+ const integrityHash = integrity.split("-")[1];
274
+ const hash = Array.from(new Uint8Array(hashBuffer)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
275
+ if (hash !== integrityHash) {
276
+ throw new Error(
277
+ `Integrity check for ${url} failed: is ${hash}, but expected ${integrityHash}`
149
278
  );
150
- const integrityHash = integrity.split("-")[1];
151
- const hash = Array.from(new Uint8Array(hashBuffer)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
152
- if (hash !== integrityHash) {
153
- throw new Error(
154
- `Integrity check for ${url} failed: is ${hash}, but expected ${integrityHash}`
155
- );
156
- }
157
279
  }
158
280
  });
159
281
  }
@@ -162,7 +284,7 @@ var SDJwtVcInstance = class _SDJwtVcInstance extends SDJwtInstance {
162
284
  * @param url
163
285
  * @returns
164
286
  */
165
- fetch(url, integrity) {
287
+ fetchWithIntegrity(url, integrity) {
166
288
  return __async(this, null, function* () {
167
289
  var _a;
168
290
  try {
@@ -176,7 +298,8 @@ var SDJwtVcInstance = class _SDJwtVcInstance extends SDJwtInstance {
176
298
  );
177
299
  }
178
300
  yield this.validateIntegrity(response.clone(), url, integrity);
179
- return response.json();
301
+ const data = yield response.json();
302
+ return data;
180
303
  } catch (error) {
181
304
  if (error.name === "TimeoutError") {
182
305
  throw new Error(`Request to ${url} timed out`);
@@ -187,59 +310,190 @@ var SDJwtVcInstance = class _SDJwtVcInstance extends SDJwtInstance {
187
310
  }
188
311
  /**
189
312
  * Verifies the VCT of the SD-JWT-VC. Returns the type metadata format.
313
+ * Resolves the full extends chain according to spec sections 6.4, 8.2, and 9.5.
190
314
  * @param result
191
315
  * @returns
192
316
  */
193
- verifyVct(result) {
317
+ fetchVct(result) {
194
318
  return __async(this, null, function* () {
195
- const typeMetadataFormat = yield this.fetchVct(result);
196
- if (typeMetadataFormat.extends) {
319
+ const typeMetadataFormat = yield this.fetchSingleVct(
320
+ result.payload.vct,
321
+ result.payload["vct#integrity"]
322
+ );
323
+ if (!typeMetadataFormat) return void 0;
324
+ if (!typeMetadataFormat.extends) {
325
+ return {
326
+ mergedTypeMetadata: typeMetadataFormat,
327
+ typeMetadataChain: [typeMetadataFormat],
328
+ vctValues: [typeMetadataFormat.vct]
329
+ };
197
330
  }
198
- return typeMetadataFormat;
331
+ return this.resolveVctExtendsChain(typeMetadataFormat);
199
332
  });
200
333
  }
201
334
  /**
202
- * Fetches VCT Metadata of the SD-JWT-VC. Returns the type metadata format. If the SD-JWT-VC does not contain a vct claim, an error is thrown.
203
- * @param result
204
- * @returns
335
+ * Checks if two claim paths are equal by comparing each element.
336
+ * @param path1 First claim path
337
+ * @param path2 Second claim path
338
+ * @returns True if paths are equal, false otherwise
205
339
  */
206
- fetchVct(result) {
207
- return __async(this, null, function* () {
208
- var _a, _b;
209
- if (!result.payload.vct) {
210
- throw new SDJWTException("vct claim is required");
340
+ claimPathsEqual(path1, path2) {
341
+ if (path1.length !== path2.length) return false;
342
+ return path1.every((element, index) => element === path2[index]);
343
+ }
344
+ /**
345
+ * Validates that extending claim metadata respects the constraints from spec section 9.5.1.
346
+ * @param baseClaim The base claim metadata
347
+ * @param extendingClaim The extending claim metadata
348
+ * @throws SDJWTException if validation fails
349
+ */
350
+ validateClaimExtension(baseClaim, extendingClaim) {
351
+ if (baseClaim.sd && extendingClaim.sd) {
352
+ if ((baseClaim.sd === "always" || baseClaim.sd === "never") && baseClaim.sd !== extendingClaim.sd) {
353
+ const pathStr = JSON.stringify(extendingClaim.path);
354
+ throw new SDJWTException(
355
+ `Cannot change 'sd' property from '${baseClaim.sd}' to '${extendingClaim.sd}' for claim at path ${pathStr}`
356
+ );
211
357
  }
212
- if ((_a = result.header) == null ? void 0 : _a.vctm) {
213
- return this.fetchVctFromHeader(result.payload.vct, result);
358
+ }
359
+ }
360
+ /**
361
+ * Merges two type metadata formats, with the extending metadata overriding the base metadata.
362
+ * According to spec section 9.5:
363
+ * - All claim metadata from the extended type are inherited
364
+ * - The child type can add new claims or properties
365
+ * - If the child type defines claim metadata with the same path as the extended type,
366
+ * the child type's object will override the corresponding object from the extended type
367
+ * According to spec section 9.5.1:
368
+ * - sd property can only be changed from 'allowed' (or omitted) to 'always' or 'never'
369
+ * - sd property cannot be changed from 'always' or 'never' to a different value
370
+ * According to spec section 8.2:
371
+ * - If the extending type defines its own display property, the original display metadata is ignored
372
+ * Note: The spec also mentions 'mandatory' property constraints, but this is not currently
373
+ * defined in the Claim type and will be validated when that property is added to the type.
374
+ * @param base The base type metadata format
375
+ * @param extending The extending type metadata format
376
+ * @returns The merged type metadata format
377
+ */
378
+ mergeTypeMetadata(base, extending) {
379
+ var _a, _b;
380
+ const merged = __spreadValues({}, extending);
381
+ if (base.claims || extending.claims) {
382
+ const baseClaims = (_a = base.claims) != null ? _a : [];
383
+ const extendingClaims = (_b = extending.claims) != null ? _b : [];
384
+ for (const extendingClaim of extendingClaims) {
385
+ const matchingBaseClaim = baseClaims.find(
386
+ (baseClaim) => this.claimPathsEqual(baseClaim.path, extendingClaim.path)
387
+ );
388
+ if (matchingBaseClaim) {
389
+ this.validateClaimExtension(matchingBaseClaim, extendingClaim);
390
+ }
391
+ }
392
+ const mergedClaims = [];
393
+ const extendedClaimsWithoutBase = [...extendingClaims];
394
+ for (const baseClaim of baseClaims) {
395
+ const extendingClaimIndex = extendedClaimsWithoutBase.findIndex(
396
+ (extendingClaim2) => this.claimPathsEqual(baseClaim.path, extendingClaim2.path)
397
+ );
398
+ const extendingClaim = extendingClaimIndex !== -1 ? extendedClaimsWithoutBase[extendingClaimIndex] : void 0;
399
+ if (extendingClaim) {
400
+ extendedClaimsWithoutBase.splice(extendingClaimIndex, 1);
401
+ }
402
+ mergedClaims.push(extendingClaim != null ? extendingClaim : baseClaim);
403
+ }
404
+ mergedClaims.push(...extendedClaimsWithoutBase);
405
+ merged.claims = mergedClaims;
406
+ }
407
+ if (!extending.display && base.display) {
408
+ merged.display = base.display;
409
+ }
410
+ return merged;
411
+ }
412
+ /**
413
+ * Resolves the full VCT chain by recursively fetching extended type metadata.
414
+ * Implements security considerations from spec section 10.3 for circular dependencies.
415
+ * @param vct The VCT URI to resolve
416
+ * @param integrity Optional integrity metadata for the VCT
417
+ * @param depth Current depth in the chain
418
+ * @param visitedVcts Set of already visited VCT URIs to detect circular dependencies
419
+ * @returns The fully resolved and merged type metadata format
420
+ */
421
+ resolveVctExtendsChain(_0) {
422
+ return __async(this, arguments, function* (parentTypeMetadata, depth = 1, visitedVcts = new Set(parentTypeMetadata.vct)) {
423
+ var _a;
424
+ const maxDepth = (_a = this.userConfig.maxVctExtendsDepth) != null ? _a : 5;
425
+ if (maxDepth !== -1 && depth > maxDepth) {
426
+ throw new SDJWTException(
427
+ `Maximum VCT extends depth of ${maxDepth} exceeded`
428
+ );
429
+ }
430
+ if (!parentTypeMetadata.extends) {
431
+ throw new SDJWTException(
432
+ `Type metadata for vct '${parentTypeMetadata.vct}' has no 'extends' field. Unable to resolve extended type metadata document.`
433
+ );
214
434
  }
215
- const fetcher = (_b = this.userConfig.vctFetcher) != null ? _b : ((uri, integrity) => this.fetch(uri, integrity));
216
- return fetcher(result.payload.vct, result.payload["vct#Integrity"]);
435
+ if (visitedVcts.has(parentTypeMetadata.extends)) {
436
+ throw new SDJWTException(
437
+ `Circular dependency detected in VCT extends chain: ${parentTypeMetadata.extends}`
438
+ );
439
+ }
440
+ visitedVcts.add(parentTypeMetadata.extends);
441
+ const extendedTypeMetadata = yield this.fetchSingleVct(
442
+ parentTypeMetadata.extends,
443
+ parentTypeMetadata["extends#integrity"]
444
+ );
445
+ if (!extendedTypeMetadata) {
446
+ throw new SDJWTException(
447
+ `Resolving VCT extends value '${parentTypeMetadata.extends}' resulted in an undefined result.`
448
+ );
449
+ }
450
+ let resolvedTypeMetadata;
451
+ if (extendedTypeMetadata.extends) {
452
+ resolvedTypeMetadata = yield this.resolveVctExtendsChain(
453
+ extendedTypeMetadata,
454
+ depth + 1,
455
+ visitedVcts
456
+ );
457
+ } else {
458
+ resolvedTypeMetadata = {
459
+ mergedTypeMetadata: extendedTypeMetadata,
460
+ typeMetadataChain: [extendedTypeMetadata],
461
+ vctValues: [extendedTypeMetadata.vct]
462
+ };
463
+ }
464
+ const mergedTypeMetadata = this.mergeTypeMetadata(
465
+ resolvedTypeMetadata.mergedTypeMetadata,
466
+ parentTypeMetadata
467
+ );
468
+ return {
469
+ mergedTypeMetadata,
470
+ typeMetadataChain: [
471
+ parentTypeMetadata,
472
+ ...resolvedTypeMetadata.typeMetadataChain
473
+ ],
474
+ vctValues: [parentTypeMetadata.vct, ...resolvedTypeMetadata.vctValues]
475
+ };
217
476
  });
218
477
  }
219
478
  /**
220
- * Fetches VCT Metadata from the header of the SD-JWT-VC. Returns the type metadata format. If the SD-JWT-VC does not contain a vct claim, an error is thrown.
479
+ * Fetches and verifies the VCT Metadata for a VCT value.
221
480
  * @param result
222
- * @param
481
+ * @returns
223
482
  */
224
- fetchVctFromHeader(vct, result) {
483
+ fetchSingleVct(vct, integrity) {
225
484
  return __async(this, null, function* () {
226
485
  var _a;
227
- const vctmHeader = (_a = result.header) == null ? void 0 : _a.vctm;
228
- if (!vctmHeader || !Array.isArray(vctmHeader)) {
229
- throw new Error("vctm claim in SD JWT header is invalid");
230
- }
231
- const typeMetadataFormat = vctmHeader.map((vctm) => {
232
- if (!(typeof vctm === "string")) {
233
- throw new Error("vctm claim in SD JWT header is invalid");
234
- }
235
- return JSON.parse(base64urlDecode(vctm));
236
- }).find((typeMetadataFormat2) => {
237
- return typeMetadataFormat2.vct === vct;
238
- });
239
- if (!typeMetadataFormat) {
240
- throw new Error("could not find VCT Metadata in JWT header");
486
+ const fetcher = (_a = this.userConfig.vctFetcher) != null ? _a : ((uri, integrity2) => this.fetchWithIntegrity(uri, integrity2));
487
+ const data = yield fetcher(vct, integrity);
488
+ if (!data) return void 0;
489
+ const validated = TypeMetadataFormatSchema.safeParse(data);
490
+ if (!validated.success) {
491
+ throw new SDJWTException(
492
+ `Invalid VCT type metadata for vct '${vct}':
493
+ ${z2.prettifyError(validated.error)}`
494
+ );
241
495
  }
242
- return typeMetadataFormat;
496
+ return validated.data;
243
497
  });
244
498
  }
245
499
  /**
@@ -278,5 +532,19 @@ var SDJwtVcInstance = class _SDJwtVcInstance extends SDJwtInstance {
278
532
  }
279
533
  };
280
534
  export {
281
- SDJwtVcInstance
535
+ ClaimDisplaySchema,
536
+ ClaimPathSchema,
537
+ ClaimSchema,
538
+ ClaimSelectiveDisclosureSchema,
539
+ ColorSchemeSchema,
540
+ ContrastSchema,
541
+ DisplaySchema,
542
+ LogoSchema,
543
+ OrientationSchema,
544
+ RenderingSchema,
545
+ SDJwtVcInstance,
546
+ SimpleRenderingSchema,
547
+ SvgTemplatePropertiesSchema,
548
+ SvgTemplateRenderingSchema,
549
+ TypeMetadataFormatSchema
282
550
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sd-jwt/sd-jwt-vc",
3
- "version": "0.17.2-next.1+9e8110d",
3
+ "version": "0.17.2-next.11+7e161c4",
4
4
  "description": "sd-jwt draft 7 implementation in typescript",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -38,13 +38,14 @@
38
38
  },
39
39
  "license": "Apache-2.0",
40
40
  "dependencies": {
41
- "@sd-jwt/core": "0.17.2-next.1+9e8110d",
42
- "@sd-jwt/jwt-status-list": "0.17.2-next.1+9e8110d",
43
- "@sd-jwt/utils": "0.17.2-next.1+9e8110d"
41
+ "@sd-jwt/core": "0.17.2-next.11+7e161c4",
42
+ "@sd-jwt/jwt-status-list": "0.17.2-next.11+7e161c4",
43
+ "@sd-jwt/utils": "0.17.2-next.11+7e161c4",
44
+ "zod": "^4.3.5"
44
45
  },
45
46
  "devDependencies": {
46
- "@sd-jwt/crypto-nodejs": "0.17.2-next.1+9e8110d",
47
- "@sd-jwt/types": "0.17.2-next.1+9e8110d",
47
+ "@sd-jwt/crypto-nodejs": "0.17.2-next.11+7e161c4",
48
+ "@sd-jwt/types": "0.17.2-next.11+7e161c4",
48
49
  "jose": "^6.1.2",
49
50
  "msw": "^2.12.3"
50
51
  },
@@ -64,5 +65,5 @@
64
65
  "esm"
65
66
  ]
66
67
  },
67
- "gitHead": "9e8110d18a7af714d9eb4aa2c3bd66ce56bf18d9"
68
+ "gitHead": "7e161c400a72bd465e82fcbbb23d3eaf332585ac"
68
69
  }
@@ -20,4 +20,6 @@ export type SDJWTVCConfig = SDJWTConfig & {
20
20
  loadTypeMetadataFormat?: boolean;
21
21
  // timeout value in milliseconds when to abort the fetch request. If not provided, it will default to 10000.
22
22
  timeout?: number;
23
+ // maximum depth of extends chain to resolve. If not provided, it will default to 5. Set to -1 to not limit the vct extends depth.
24
+ maxVctExtendsDepth?: number;
23
25
  };