@vidos-id/openid4vc-issuer 0.0.0-test1

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 ADDED
@@ -0,0 +1,670 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { SignJWT, calculateJwkThumbprint, decodeProtectedHeader, exportJWK, exportPKCS8, exportSPKI, generateKeyPair, importJWK, jwtVerify } from "jose";
6
+ import { z } from "zod";
7
+ import { SDJwt } from "@sd-jwt/core";
8
+ import { hasher } from "@sd-jwt/hash";
9
+ import { SDJwtVcInstance } from "@sd-jwt/sd-jwt-vc";
10
+ import { deflateSync } from "node:zlib";
11
+ import { randomBytes } from "node:crypto";
12
+ //#region src/schemas.ts
13
+ const jwkSchema = z.object({
14
+ kty: z.string().min(1),
15
+ kid: z.string().min(1).optional(),
16
+ alg: z.string().min(1).optional(),
17
+ crv: z.string().min(1).optional(),
18
+ x: z.string().min(1).optional(),
19
+ y: z.string().min(1).optional(),
20
+ d: z.string().min(1).optional(),
21
+ n: z.string().min(1).optional(),
22
+ e: z.string().min(1).optional(),
23
+ use: z.string().min(1).optional(),
24
+ key_ops: z.array(z.string().min(1)).optional(),
25
+ x5c: z.array(z.string().min(1)).optional()
26
+ }).catchall(z.unknown());
27
+ const claimSetSchema = z.record(z.string(), z.unknown());
28
+ const credentialConfigurationSchema = z.object({
29
+ format: z.literal("dc+sd-jwt"),
30
+ vct: z.string().min(1),
31
+ scope: z.string().min(1).optional(),
32
+ claims: z.record(z.string(), z.unknown()).optional(),
33
+ proof_signing_alg_values_supported: z.array(z.string().min(1)).default([
34
+ "ES256",
35
+ "ES384",
36
+ "EdDSA"
37
+ ])
38
+ });
39
+ const signingAlgSchema = z.enum([
40
+ "ES256",
41
+ "ES384",
42
+ "EdDSA"
43
+ ]);
44
+ const statusListBitsSchema = z.union([
45
+ z.literal(1),
46
+ z.literal(2),
47
+ z.literal(4),
48
+ z.literal(8)
49
+ ]);
50
+ const tokenStatusValueSchema = z.number().int().min(0).max(255);
51
+ const credentialStatusListReferenceSchema = z.object({
52
+ idx: z.number().int().nonnegative(),
53
+ uri: z.string().min(1)
54
+ });
55
+ const credentialStatusSchema = z.object({ status_list: credentialStatusListReferenceSchema });
56
+ const statusListRecordSchema = z.object({
57
+ uri: z.string().min(1),
58
+ bits: statusListBitsSchema.default(1),
59
+ statuses: z.array(tokenStatusValueSchema).default([]),
60
+ ttl: z.number().int().positive().optional(),
61
+ expiresAt: z.number().int().positive().optional(),
62
+ aggregation_uri: z.string().min(1).optional()
63
+ });
64
+ const createStatusListInputSchema = statusListRecordSchema.omit({ statuses: true });
65
+ const allocateCredentialStatusInputSchema = z.object({
66
+ statusList: statusListRecordSchema,
67
+ status: tokenStatusValueSchema.default(0)
68
+ });
69
+ const updateCredentialStatusInputSchema = z.object({
70
+ statusList: statusListRecordSchema,
71
+ idx: z.number().int().nonnegative(),
72
+ status: tokenStatusValueSchema
73
+ });
74
+ const issuerConfigSchema = z.object({
75
+ issuer: z.string().url(),
76
+ credentialConfigurationsSupported: z.record(z.string(), credentialConfigurationSchema).refine((value) => Object.keys(value).length > 0, "At least one credential configuration is required"),
77
+ signingKey: z.object({
78
+ alg: signingAlgSchema.default("EdDSA"),
79
+ privateJwk: jwkSchema,
80
+ publicJwk: jwkSchema
81
+ }),
82
+ endpoints: z.object({
83
+ token: z.string().url().optional(),
84
+ credential: z.string().url().optional(),
85
+ nonce: z.string().url().optional()
86
+ }).optional(),
87
+ nonceTtlSeconds: z.number().int().positive().default(300),
88
+ grantTtlSeconds: z.number().int().positive().default(600),
89
+ tokenTtlSeconds: z.number().int().positive().default(600)
90
+ });
91
+ const createPreAuthorizedGrantInputSchema = z.object({
92
+ credential_configuration_id: z.string().min(1),
93
+ claims: claimSetSchema,
94
+ expires_in: z.number().int().positive().optional()
95
+ });
96
+ const createCredentialOfferInputSchema = createPreAuthorizedGrantInputSchema;
97
+ const preAuthorizedGrantTypeSchema = z.literal("urn:ietf:params:oauth:grant-type:pre-authorized_code");
98
+ const credentialOfferGrantSchema = z.object({
99
+ "pre-authorized_code": z.string().min(1),
100
+ tx_code: z.never().optional()
101
+ });
102
+ const credentialOfferSchema = z.object({
103
+ credential_issuer: z.string().url(),
104
+ credential_configuration_ids: z.array(z.string().min(1)).min(1).max(1),
105
+ grants: z.object({ "urn:ietf:params:oauth:grant-type:pre-authorized_code": credentialOfferGrantSchema })
106
+ });
107
+ const credentialOfferUriSchema = z.string().min(1).refine((value) => {
108
+ try {
109
+ return new URL(value).protocol === "openid-credential-offer:";
110
+ } catch {
111
+ return false;
112
+ }
113
+ }, "Credential offer URI must use openid-credential-offer://");
114
+ const issuerMetadataCredentialConfigurationSchema = z.object({
115
+ format: z.literal("dc+sd-jwt"),
116
+ vct: z.string().min(1),
117
+ scope: z.string().min(1),
118
+ proof_types_supported: z.object({ jwt: z.object({ proof_signing_alg_values_supported: z.array(z.string().min(1)).min(1) }) }),
119
+ cryptographic_binding_methods_supported: z.array(z.literal("jwk")).min(1),
120
+ credential_signing_alg_values_supported: z.array(z.string().min(1)).min(1)
121
+ });
122
+ const issuerMetadataSchema = z.object({
123
+ credential_issuer: z.string().url(),
124
+ token_endpoint: z.string().url(),
125
+ credential_endpoint: z.string().url(),
126
+ nonce_endpoint: z.string().url().optional(),
127
+ jwks: z.object({ keys: z.array(jwkSchema).min(1) }),
128
+ credential_configurations_supported: z.record(z.string(), issuerMetadataCredentialConfigurationSchema)
129
+ });
130
+ const preAuthorizedGrantRecordSchema = z.object({
131
+ preAuthorizedCode: z.string().min(1),
132
+ credentialConfigurationId: z.string().min(1),
133
+ claims: claimSetSchema,
134
+ expiresAt: z.number().int(),
135
+ used: z.boolean()
136
+ });
137
+ const tokenRequestSchema = z.object({
138
+ grant_type: preAuthorizedGrantTypeSchema,
139
+ "pre-authorized_code": z.string().min(1),
140
+ tx_code: z.string().min(1).optional()
141
+ });
142
+ const tokenResponseSchema = z.object({
143
+ access_token: z.string().min(1),
144
+ token_type: z.literal("Bearer"),
145
+ expires_in: z.number().int().positive(),
146
+ credential_configuration_id: z.string().min(1),
147
+ c_nonce: z.string().min(1).optional(),
148
+ c_nonce_expires_in: z.number().int().positive().optional()
149
+ });
150
+ const exchangePreAuthorizedCodeInputSchema = z.object({
151
+ tokenRequest: tokenRequestSchema,
152
+ preAuthorizedGrant: preAuthorizedGrantRecordSchema
153
+ });
154
+ const accessTokenRecordSchema = z.object({
155
+ accessToken: z.string().min(1),
156
+ credentialConfigurationId: z.string().min(1),
157
+ claims: claimSetSchema,
158
+ expiresAt: z.number().int(),
159
+ used: z.boolean()
160
+ });
161
+ const issueCredentialInputSchema = z.object({
162
+ accessToken: accessTokenRecordSchema,
163
+ credential_configuration_id: z.string().min(1),
164
+ holderPublicJwk: jwkSchema.optional(),
165
+ status: credentialStatusSchema.optional()
166
+ });
167
+ const nonceRecordSchema = z.object({
168
+ c_nonce: z.string().min(1),
169
+ expiresAt: z.number().int(),
170
+ used: z.boolean()
171
+ });
172
+ const nonceResponseSchema = z.object({
173
+ c_nonce: z.string().min(1),
174
+ c_nonce_expires_in: z.number().int().positive()
175
+ });
176
+ const credentialRequestProofSchema = z.object({
177
+ proof_type: z.literal("jwt"),
178
+ jwt: z.string().min(1)
179
+ });
180
+ const credentialRequestSchema = z.object({
181
+ format: z.literal("dc+sd-jwt").optional(),
182
+ credential_configuration_id: z.string().min(1),
183
+ proofs: z.object({ jwt: z.array(credentialRequestProofSchema).min(1) })
184
+ });
185
+ const credentialResponseSchema = z.object({
186
+ format: z.literal("dc+sd-jwt"),
187
+ credential: z.string().min(1),
188
+ c_nonce: z.string().min(1).optional(),
189
+ c_nonce_expires_in: z.number().int().positive().optional()
190
+ });
191
+ const validateProofJwtInputSchema = z.object({
192
+ jwt: z.string().min(1),
193
+ nonce: nonceRecordSchema
194
+ });
195
+ //#endregion
196
+ //#region src/crypto.ts
197
+ const cleanupFingerprint = (value) => value.replace(/^sha256 fingerprint=/i, "").replaceAll(":", "").trim();
198
+ const certificatePemToX5c = (certificatePem) => [certificatePem.replace("-----BEGIN CERTIFICATE-----", "").replace("-----END CERTIFICATE-----", "").replace(/\s+/g, "")];
199
+ const generateIssuerTrustMaterial = async (input) => {
200
+ const kid = input?.kid ?? "issuer-key-1";
201
+ const subject = input?.subject ?? "/CN=Demo Issuer/O=openid4vc-tools";
202
+ const daysValid = input?.daysValid ?? 365;
203
+ const alg = input?.alg ?? "EdDSA";
204
+ const { privateKey, publicKey } = alg === "EdDSA" ? await generateKeyPair("EdDSA", {
205
+ crv: "Ed25519",
206
+ extractable: true
207
+ }) : await generateKeyPair(alg, { extractable: true });
208
+ const privateJwk = jwkSchema.parse({
209
+ ...await exportJWK(privateKey),
210
+ kid,
211
+ alg,
212
+ use: "sig"
213
+ });
214
+ const publicJwk = jwkSchema.parse({
215
+ ...await exportJWK(publicKey),
216
+ kid,
217
+ alg,
218
+ use: "sig"
219
+ });
220
+ const privateKeyPem = await exportPKCS8(privateKey);
221
+ const publicKeyPem = await exportSPKI(publicKey);
222
+ const dir = await mkdtemp(join(tmpdir(), "issuer-trust-"));
223
+ const keyPath = join(dir, "issuer-key.pem");
224
+ const certPath = join(dir, "issuer-cert.pem");
225
+ try {
226
+ await writeFile(keyPath, privateKeyPem, "utf8");
227
+ execFileSync("openssl", [
228
+ "req",
229
+ "-x509",
230
+ "-new",
231
+ "-key",
232
+ keyPath,
233
+ "-out",
234
+ certPath,
235
+ "-subj",
236
+ subject,
237
+ "-days",
238
+ String(daysValid)
239
+ ], { stdio: "ignore" });
240
+ const certificatePem = await readFile(certPath, "utf8");
241
+ const certificateFingerprintSha256 = cleanupFingerprint(execFileSync("openssl", [
242
+ "x509",
243
+ "-noout",
244
+ "-fingerprint",
245
+ "-sha256",
246
+ "-in",
247
+ certPath
248
+ ], { encoding: "utf8" }));
249
+ const x5c = certificatePemToX5c(certificatePem);
250
+ const publicJwkWithCertificate = jwkSchema.parse({
251
+ ...publicJwk,
252
+ x5c
253
+ });
254
+ const jwks = { keys: [publicJwkWithCertificate] };
255
+ return {
256
+ alg,
257
+ kid,
258
+ privateJwk,
259
+ publicJwk: publicJwkWithCertificate,
260
+ privateKeyPem,
261
+ publicKeyPem,
262
+ certificatePem,
263
+ certificateFingerprintSha256,
264
+ jwks,
265
+ trustArtifact: {
266
+ kid,
267
+ alg,
268
+ jwks,
269
+ publicKeyPem,
270
+ certificatePem,
271
+ certificateFingerprintSha256
272
+ }
273
+ };
274
+ } finally {
275
+ await rm(dir, {
276
+ recursive: true,
277
+ force: true
278
+ });
279
+ }
280
+ };
281
+ //#endregion
282
+ //#region src/errors.ts
283
+ var IssuerError = class extends Error {
284
+ constructor(code, message) {
285
+ super(message);
286
+ this.code = code;
287
+ this.name = "IssuerError";
288
+ }
289
+ };
290
+ //#endregion
291
+ //#region src/openid4vci.ts
292
+ const OPENID_CREDENTIAL_OFFER_WELL_KNOWN = "/.well-known/openid-credential-issuer";
293
+ function createIssuerMetadata(config) {
294
+ const parsed = issuerConfigSchema.parse(config);
295
+ const tokenEndpoint = parsed.endpoints?.token ?? new URL("/token", parsed.issuer).toString();
296
+ const credentialEndpoint = parsed.endpoints?.credential ?? new URL("/credential", parsed.issuer).toString();
297
+ const nonceEndpoint = parsed.endpoints?.nonce ?? new URL("/nonce", parsed.issuer).toString();
298
+ return issuerMetadataSchema.parse({
299
+ credential_issuer: parsed.issuer,
300
+ token_endpoint: tokenEndpoint,
301
+ credential_endpoint: credentialEndpoint,
302
+ nonce_endpoint: nonceEndpoint,
303
+ jwks: { keys: [parsed.signingKey.publicJwk] },
304
+ credential_configurations_supported: Object.fromEntries(Object.entries(parsed.credentialConfigurationsSupported).map(([id, entry]) => [id, {
305
+ format: entry.format,
306
+ vct: entry.vct,
307
+ scope: entry.scope ?? id,
308
+ proof_types_supported: { jwt: { proof_signing_alg_values_supported: entry.proof_signing_alg_values_supported } },
309
+ cryptographic_binding_methods_supported: ["jwk"],
310
+ credential_signing_alg_values_supported: [parsed.signingKey.alg]
311
+ }]))
312
+ });
313
+ }
314
+ function serializeCredentialOfferUri(offer) {
315
+ const parsed = credentialOfferSchema.parse(offer);
316
+ const url = new URL("openid-credential-offer://");
317
+ url.searchParams.set("credential_offer", JSON.stringify(parsed));
318
+ return credentialOfferUriSchema.parse(url.toString());
319
+ }
320
+ function serializeCredentialOfferReferenceUri(credentialOfferUrl) {
321
+ const parsed = new URL(credentialOfferUrl).toString();
322
+ const url = new URL("openid-credential-offer://");
323
+ url.searchParams.set("credential_offer_uri", parsed);
324
+ return credentialOfferUriSchema.parse(url.toString());
325
+ }
326
+ function getCredentialIssuerMetadataUrl(credentialIssuer) {
327
+ const issuerUrl = new URL(credentialIssuer);
328
+ const issuerPath = issuerUrl.pathname === "/" ? "" : issuerUrl.pathname;
329
+ return new URL(`${OPENID_CREDENTIAL_OFFER_WELL_KNOWN}${issuerPath}`, issuerUrl.origin).toString();
330
+ }
331
+ //#endregion
332
+ //#region src/status-list.ts
333
+ const maxStatusValue = (bits) => (1 << bits) - 1;
334
+ function assertStatusValueFits(bits, value) {
335
+ if (value > maxStatusValue(bits)) throw new Error(`Status value ${value} does not fit in a ${bits}-bit status list`);
336
+ }
337
+ function createStatusList(input) {
338
+ const parsed = createStatusListInputSchema.parse(input);
339
+ return statusListRecordSchema.parse({
340
+ ...parsed,
341
+ statuses: []
342
+ });
343
+ }
344
+ function allocateCredentialStatus(input) {
345
+ const parsed = allocateCredentialStatusInputSchema.parse(input);
346
+ assertStatusValueFits(parsed.statusList.bits, parsed.status);
347
+ const idx = parsed.statusList.statuses.length;
348
+ const updatedStatusList = statusListRecordSchema.parse({
349
+ ...parsed.statusList,
350
+ statuses: [...parsed.statusList.statuses, parsed.status]
351
+ });
352
+ return {
353
+ credentialStatus: { status_list: {
354
+ idx,
355
+ uri: parsed.statusList.uri
356
+ } },
357
+ updatedStatusList
358
+ };
359
+ }
360
+ function updateCredentialStatus(input) {
361
+ const parsed = updateCredentialStatusInputSchema.parse(input);
362
+ assertStatusValueFits(parsed.statusList.bits, parsed.status);
363
+ if (parsed.idx >= parsed.statusList.statuses.length) throw new Error(`Status list index ${parsed.idx} is out of bounds`);
364
+ const statuses = [...parsed.statusList.statuses];
365
+ statuses[parsed.idx] = parsed.status;
366
+ return statusListRecordSchema.parse({
367
+ ...parsed.statusList,
368
+ statuses
369
+ });
370
+ }
371
+ function encodeStatusList(statusList) {
372
+ const parsed = statusListRecordSchema.parse(statusList);
373
+ const packed = packStatuses(parsed.statuses, parsed.bits);
374
+ return {
375
+ bits: parsed.bits,
376
+ lst: deflateSync(packed).toString("base64url"),
377
+ aggregation_uri: parsed.aggregation_uri
378
+ };
379
+ }
380
+ async function createStatusListJwt(input) {
381
+ const payload = encodeStatusList(input.statusList);
382
+ const claims = {
383
+ iss: input.issuer,
384
+ status_list: payload
385
+ };
386
+ if (input.statusList.ttl) claims.ttl = input.statusList.ttl;
387
+ const jwt = new SignJWT(claims).setProtectedHeader({
388
+ alg: input.signingKey.alg,
389
+ typ: "statuslist+jwt",
390
+ kid: input.signingKey.publicJwk.kid,
391
+ x5c: input.signingKey.publicJwk.x5c
392
+ }).setSubject(input.statusList.uri).setIssuedAt(input.now());
393
+ if (input.statusList.expiresAt) jwt.setExpirationTime(input.statusList.expiresAt);
394
+ return jwt.sign(input.signingKey.privateKey);
395
+ }
396
+ function packStatuses(statuses, bits) {
397
+ const byteLength = Math.ceil(statuses.length * bits / 8);
398
+ const output = new Uint8Array(byteLength);
399
+ for (const [idx, status] of statuses.entries()) {
400
+ assertStatusValueFits(bits, status);
401
+ const bitOffset = idx * bits;
402
+ const byteIndex = Math.floor(bitOffset / 8);
403
+ const intraByteOffset = bitOffset % 8;
404
+ output[byteIndex] = (output[byteIndex] ?? 0) | status << intraByteOffset;
405
+ }
406
+ return output;
407
+ }
408
+ //#endregion
409
+ //#region src/utils.ts
410
+ const nowInSeconds = () => Math.floor(Date.now() / 1e3);
411
+ const randomToken = () => randomBytes(24).toString("base64url");
412
+ const cloneJson = (value) => structuredClone(value);
413
+ const toBase64Url = (input) => Buffer.from(input).toString("base64url");
414
+ const fromBase64Url = (input) => new Uint8Array(Buffer.from(input, "base64url"));
415
+ //#endregion
416
+ //#region src/issuer.ts
417
+ const RESERVED_CREDENTIAL_CLAIMS = new Set([
418
+ "iss",
419
+ "nbf",
420
+ "exp",
421
+ "cnf",
422
+ "vct",
423
+ "status",
424
+ "iat"
425
+ ]);
426
+ const deriveDisclosureFrame = (claims) => {
427
+ const topLevelClaims = Object.keys(claims).filter((claim) => !RESERVED_CREDENTIAL_CLAIMS.has(claim));
428
+ if (topLevelClaims.length === 0) return;
429
+ return { _sd: topLevelClaims };
430
+ };
431
+ const sanitizeCredentialClaims = (claims) => Object.fromEntries(Object.entries(claims).filter(([claim]) => !RESERVED_CREDENTIAL_CLAIMS.has(claim)));
432
+ const subtleAlgorithm = (alg) => {
433
+ if (alg === "EdDSA") return "Ed25519";
434
+ if (alg === "ES384") return {
435
+ name: "ECDSA",
436
+ hash: "SHA-384"
437
+ };
438
+ return {
439
+ name: "ECDSA",
440
+ hash: "SHA-256"
441
+ };
442
+ };
443
+ const createSdJwtSigner = (privateKey, alg) => async (input) => {
444
+ const signature = await crypto.subtle.sign(subtleAlgorithm(alg), privateKey, new TextEncoder().encode(input));
445
+ return toBase64Url(new Uint8Array(signature));
446
+ };
447
+ const createSdJwtVerifier = (publicKey, alg) => async (input, signature) => {
448
+ return crypto.subtle.verify(subtleAlgorithm(alg), publicKey, fromBase64Url(signature), new TextEncoder().encode(input));
449
+ };
450
+ const holderJwkSchema = jwkSchema.refine((value) => Boolean(value.kty && (value.x || value.n)), "Holder proof must contain an embedded public JWK");
451
+ const proofPayloadSchema = z.object({
452
+ aud: z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]),
453
+ nonce: z.string().min(1),
454
+ cnf: z.object({ jwk: holderJwkSchema }).optional()
455
+ });
456
+ var DemoIssuer = class {
457
+ config;
458
+ now;
459
+ idGenerator;
460
+ issuerPrivateKeyPromise;
461
+ issuerPublicKeyPromise;
462
+ sdJwtVc;
463
+ constructor(config, options) {
464
+ this.config = issuerConfigSchema.parse(config);
465
+ this.now = options?.now ?? nowInSeconds;
466
+ this.idGenerator = options?.idGenerator ?? randomToken;
467
+ this.issuerPrivateKeyPromise = importJWK(this.config.signingKey.privateJwk, this.config.signingKey.alg, { extractable: false });
468
+ this.issuerPublicKeyPromise = importJWK(this.config.signingKey.publicJwk, this.config.signingKey.alg, { extractable: true });
469
+ this.sdJwtVc = Promise.all([this.issuerPrivateKeyPromise, this.issuerPublicKeyPromise]).then(([privateKey, publicKey]) => new SDJwtVcInstance({
470
+ signer: createSdJwtSigner(privateKey, this.config.signingKey.alg),
471
+ signAlg: this.config.signingKey.alg,
472
+ verifier: createSdJwtVerifier(publicKey, this.config.signingKey.alg),
473
+ hasher,
474
+ hashAlg: "sha-256",
475
+ saltGenerator: async (length = 16) => toBase64Url(crypto.getRandomValues(new Uint8Array(length)))
476
+ }));
477
+ }
478
+ getJwks() {
479
+ return { keys: [cloneJson(this.config.signingKey.publicJwk)] };
480
+ }
481
+ getMetadata() {
482
+ return issuerMetadataSchema.parse(createIssuerMetadata(this.config));
483
+ }
484
+ createStatusList(input) {
485
+ return createStatusList(input);
486
+ }
487
+ allocateCredentialStatus(input) {
488
+ return allocateCredentialStatus(input);
489
+ }
490
+ updateCredentialStatus(input) {
491
+ return updateCredentialStatus(input);
492
+ }
493
+ async createStatusListToken(statusList) {
494
+ return createStatusListJwt({
495
+ issuer: this.config.issuer,
496
+ signingKey: {
497
+ alg: this.config.signingKey.alg,
498
+ privateKey: await this.issuerPrivateKeyPromise,
499
+ publicJwk: this.config.signingKey.publicJwk
500
+ },
501
+ statusList,
502
+ now: this.now
503
+ });
504
+ }
505
+ createPreAuthorizedGrant(input) {
506
+ const parsed = createPreAuthorizedGrantInputSchema.parse(input);
507
+ if (!this.config.credentialConfigurationsSupported[parsed.credential_configuration_id]) throw new IssuerError("unsupported_credential_configuration", "Unsupported credential_configuration_id");
508
+ const preAuthorizedCode = this.idGenerator();
509
+ const expiresAt = this.now() + (parsed.expires_in ?? this.config.grantTtlSeconds);
510
+ const preAuthorizedGrant = {
511
+ preAuthorizedCode,
512
+ credentialConfigurationId: parsed.credential_configuration_id,
513
+ claims: cloneJson(parsed.claims),
514
+ expiresAt,
515
+ used: false
516
+ };
517
+ return {
518
+ preAuthorizedCode,
519
+ expiresAt,
520
+ credential_configuration_id: parsed.credential_configuration_id,
521
+ preAuthorizedGrant
522
+ };
523
+ }
524
+ createCredentialOffer(input) {
525
+ const parsed = createCredentialOfferInputSchema.parse(input);
526
+ const grant = this.createPreAuthorizedGrant(parsed);
527
+ return {
528
+ ...credentialOfferSchema.parse({
529
+ credential_issuer: this.config.issuer,
530
+ credential_configuration_ids: [grant.credential_configuration_id],
531
+ grants: { "urn:ietf:params:oauth:grant-type:pre-authorized_code": { "pre-authorized_code": grant.preAuthorizedCode } }
532
+ }),
533
+ preAuthorizedGrant: grant.preAuthorizedGrant
534
+ };
535
+ }
536
+ createCredentialOfferUri(input) {
537
+ return serializeCredentialOfferUri(this.createCredentialOffer(input));
538
+ }
539
+ createCredentialOfferReferenceUri(credentialOfferUrl) {
540
+ return serializeCredentialOfferReferenceUri(credentialOfferUrl);
541
+ }
542
+ exchangePreAuthorizedCode(input) {
543
+ const parsed = exchangePreAuthorizedCodeInputSchema.parse(input);
544
+ if (parsed.tokenRequest.tx_code) throw new IssuerError("unsupported_tx_code", "tx_code is not supported in this demo issuer");
545
+ if (parsed.preAuthorizedGrant.preAuthorizedCode !== parsed.tokenRequest["pre-authorized_code"]) throw new IssuerError("invalid_grant", "Invalid or expired pre-authorized code");
546
+ if (parsed.preAuthorizedGrant.used || parsed.preAuthorizedGrant.expiresAt <= this.now()) throw new IssuerError("invalid_grant", "Invalid or expired pre-authorized code");
547
+ const accessToken = this.idGenerator();
548
+ const expiresIn = this.config.tokenTtlSeconds;
549
+ const updatedPreAuthorizedGrant = {
550
+ ...parsed.preAuthorizedGrant,
551
+ used: true
552
+ };
553
+ const accessTokenRecord = {
554
+ accessToken,
555
+ credentialConfigurationId: parsed.preAuthorizedGrant.credentialConfigurationId,
556
+ claims: cloneJson(parsed.preAuthorizedGrant.claims),
557
+ expiresAt: this.now() + expiresIn,
558
+ used: false
559
+ };
560
+ return {
561
+ access_token: accessToken,
562
+ token_type: "Bearer",
563
+ expires_in: expiresIn,
564
+ credential_configuration_id: parsed.preAuthorizedGrant.credentialConfigurationId,
565
+ accessTokenRecord,
566
+ updatedPreAuthorizedGrant
567
+ };
568
+ }
569
+ createNonce() {
570
+ const c_nonce = this.idGenerator();
571
+ const expiresIn = this.config.nonceTtlSeconds;
572
+ return {
573
+ c_nonce,
574
+ c_nonce_expires_in: expiresIn,
575
+ nonce: {
576
+ c_nonce,
577
+ expiresAt: this.now() + expiresIn,
578
+ used: false
579
+ }
580
+ };
581
+ }
582
+ async validateProofJwt(input) {
583
+ const parsed = validateProofJwtInputSchema.parse(input);
584
+ const protectedHeader = decodeProtectedHeader(parsed.jwt);
585
+ if (protectedHeader.typ !== "openid4vci-proof+jwt") throw new IssuerError("invalid_proof", "Proof JWT typ must be openid4vci-proof+jwt");
586
+ if (typeof protectedHeader.alg !== "string" || protectedHeader.alg.length === 0) throw new IssuerError("invalid_proof", "Proof JWT alg is required");
587
+ const embeddedJwk = holderJwkSchema.safeParse(protectedHeader.jwk);
588
+ if (!embeddedJwk.success) throw new IssuerError("invalid_proof", "Proof JWT must contain an embedded public JWK in the protected header");
589
+ const importedFromHeader = await importJWK(embeddedJwk.data, protectedHeader.alg, { extractable: true });
590
+ let verified;
591
+ try {
592
+ verified = await jwtVerify(parsed.jwt, importedFromHeader, { audience: this.config.issuer });
593
+ } catch (error) {
594
+ throw new IssuerError("invalid_proof", error instanceof Error ? error.message : "Proof JWT verification failed");
595
+ }
596
+ const payload = proofPayloadSchema.parse(verified.payload);
597
+ if (parsed.nonce.c_nonce !== payload.nonce) throw new IssuerError("invalid_proof", "Proof JWT nonce is invalid or expired");
598
+ if (parsed.nonce.used || parsed.nonce.expiresAt <= this.now()) throw new IssuerError("invalid_proof", "Proof JWT nonce is invalid or expired");
599
+ const holderPublicJwk = embeddedJwk.data;
600
+ const updatedNonce = {
601
+ ...parsed.nonce,
602
+ used: true
603
+ };
604
+ return {
605
+ nonce: payload.nonce,
606
+ holderPublicJwk,
607
+ holderKeyThumbprint: await calculateJwkThumbprint(holderPublicJwk),
608
+ payload: cloneJson(verified?.payload),
609
+ protectedHeader: cloneJson(protectedHeader),
610
+ updatedNonce
611
+ };
612
+ }
613
+ async issueCredential(input) {
614
+ const parsed = issueCredentialInputSchema.parse(input);
615
+ if (parsed.accessToken.used || parsed.accessToken.expiresAt <= this.now()) throw new IssuerError("invalid_token", "Invalid or expired access token");
616
+ if (parsed.accessToken.credentialConfigurationId !== parsed.credential_configuration_id) throw new IssuerError("invalid_request", "Access token is not valid for credential_configuration_id");
617
+ const configuration = this.config.credentialConfigurationsSupported[parsed.credential_configuration_id];
618
+ if (!configuration) throw new IssuerError("unsupported_credential_configuration", "Unsupported credential_configuration_id");
619
+ const binding = input.proof ? {
620
+ holderPublicJwk: input.proof.holderPublicJwk,
621
+ holderKeyThumbprint: input.proof.holderKeyThumbprint
622
+ } : parsed.holderPublicJwk ? {
623
+ holderPublicJwk: parsed.holderPublicJwk,
624
+ holderKeyThumbprint: await calculateJwkThumbprint(parsed.holderPublicJwk)
625
+ } : {};
626
+ const sdJwtVc = await this.sdJwtVc;
627
+ const issuedAt = this.now();
628
+ const credentialClaims = sanitizeCredentialClaims(parsed.accessToken.claims);
629
+ const payload = {
630
+ iss: this.config.issuer,
631
+ iat: issuedAt,
632
+ vct: configuration.vct,
633
+ status: parsed.status ? cloneJson(parsed.status) : void 0,
634
+ cnf: binding.holderPublicJwk ? { jwk: binding.holderPublicJwk } : void 0,
635
+ ...cloneJson(credentialClaims)
636
+ };
637
+ const credential = await sdJwtVc.issue(payload, deriveDisclosureFrame(credentialClaims), { header: {
638
+ kid: this.config.signingKey.publicJwk.kid,
639
+ x5c: this.config.signingKey.publicJwk.x5c
640
+ } });
641
+ const nonce = this.createNonce();
642
+ const updatedAccessToken = {
643
+ ...parsed.accessToken,
644
+ used: true
645
+ };
646
+ return {
647
+ ...credentialResponseSchema.parse({
648
+ format: "dc+sd-jwt",
649
+ credential,
650
+ c_nonce: nonce.c_nonce
651
+ }),
652
+ format: "dc+sd-jwt",
653
+ nonce: nonce.nonce,
654
+ updatedAccessToken
655
+ };
656
+ }
657
+ async parseIssuedCredential(encoded) {
658
+ const sdJwt = await SDJwt.fromEncode(encoded, hasher);
659
+ const jwt = await SDJwt.extractJwt(encoded);
660
+ return {
661
+ jwt: jwt.encodeJwt(),
662
+ header: jwt.header,
663
+ payload: jwt.payload,
664
+ claims: await sdJwt.getClaims(hasher)
665
+ };
666
+ }
667
+ };
668
+ const createIssuer = (config, options) => new DemoIssuer(config, options);
669
+ //#endregion
670
+ export { DemoIssuer, IssuerError, accessTokenRecordSchema, allocateCredentialStatus, claimSetSchema, createCredentialOfferInputSchema, createIssuer, createIssuerMetadata, createPreAuthorizedGrantInputSchema, createStatusList, createStatusListInputSchema, createStatusListJwt, credentialConfigurationSchema, credentialOfferSchema, credentialOfferUriSchema, credentialRequestProofSchema, credentialRequestSchema, credentialResponseSchema, credentialStatusListReferenceSchema, credentialStatusSchema, encodeStatusList, exchangePreAuthorizedCodeInputSchema, generateIssuerTrustMaterial, getCredentialIssuerMetadataUrl, issueCredentialInputSchema, issuerConfigSchema, issuerMetadataCredentialConfigurationSchema, issuerMetadataSchema, jwkSchema, nonceRecordSchema, nonceResponseSchema, preAuthorizedGrantRecordSchema, preAuthorizedGrantTypeSchema, serializeCredentialOfferReferenceUri, serializeCredentialOfferUri, signingAlgSchema, statusListBitsSchema, statusListRecordSchema, tokenRequestSchema, tokenResponseSchema, tokenStatusValueSchema, updateCredentialStatus, validateProofJwtInputSchema };