@openeudi/openid4vp 0.1.0

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.
@@ -0,0 +1,125 @@
1
+ interface IssuerInfo {
2
+ certificate: Uint8Array;
3
+ country: string;
4
+ name?: string;
5
+ organization?: string;
6
+ }
7
+
8
+ type CredentialFormat = 'sd-jwt-vc' | 'mdoc';
9
+ interface CredentialClaims {
10
+ age_over_18?: boolean;
11
+ age_over_21?: boolean;
12
+ resident_country?: string;
13
+ nationality?: string;
14
+ family_name_birth?: string;
15
+ [key: string]: unknown;
16
+ }
17
+ interface PresentationResult {
18
+ valid: boolean;
19
+ format: CredentialFormat;
20
+ claims: CredentialClaims;
21
+ issuer: IssuerInfo;
22
+ error?: string;
23
+ }
24
+
25
+ interface AuthorizationRequestInput {
26
+ requestedAttributes: string[];
27
+ acceptedFormats: CredentialFormat[];
28
+ responseUri: string;
29
+ nonce: string;
30
+ clientId: string;
31
+ state?: string;
32
+ }
33
+ interface AuthorizationRequest {
34
+ uri: string;
35
+ presentationDefinition: object;
36
+ nonce: string;
37
+ state: string;
38
+ }
39
+
40
+ interface ParseOptions {
41
+ trustedCertificates: Uint8Array[];
42
+ nonce: string;
43
+ }
44
+ interface ICredentialParser {
45
+ readonly format: CredentialFormat;
46
+ canParse(vpToken: unknown): boolean;
47
+ parse(vpToken: unknown, options: ParseOptions): Promise<PresentationResult>;
48
+ }
49
+
50
+ /**
51
+ * SD-JWT VC credential parser.
52
+ *
53
+ * Decodes and validates SD-JWT Verifiable Credentials as specified in
54
+ * the EUDI Wallet ecosystem. Performs:
55
+ * - Structure validation (JWT~disclosure~kb format)
56
+ * - Issuer certificate trust chain verification
57
+ * - Expiry checking
58
+ * - Nonce validation against the key binding JWT
59
+ * - Selective disclosure claim extraction
60
+ */
61
+ declare class SdJwtParser implements ICredentialParser {
62
+ readonly format: CredentialFormat;
63
+ /**
64
+ * Returns true when the token looks like an SD-JWT:
65
+ * it must be a string containing the `~` disclosure separator.
66
+ */
67
+ canParse(vpToken: unknown): boolean;
68
+ /**
69
+ * Parses and validates an SD-JWT VC token.
70
+ *
71
+ * @throws {MalformedCredentialError} when the token structure cannot be decoded
72
+ */
73
+ parse(vpToken: unknown, options: ParseOptions): Promise<PresentationResult>;
74
+ }
75
+
76
+ /**
77
+ * mDOC / mDL credential parser.
78
+ *
79
+ * Decodes and validates mDOC (Mobile Document) credentials as specified in
80
+ * ISO 18013-5 and the EUDI Wallet ecosystem. Processes CBOR-encoded
81
+ * DeviceResponse structures containing:
82
+ * - Document type validation (eu.europa.ec.eudi.pid.1)
83
+ * - Issuer certificate trust chain verification via COSE_Sign1 issuerAuth
84
+ * - Validity period checking
85
+ * - Claim extraction from IssuerSignedItem nameSpace elements
86
+ */
87
+ declare class MdocParser implements ICredentialParser {
88
+ readonly format: CredentialFormat;
89
+ /**
90
+ * Returns true when the token is binary data (Uint8Array or Buffer),
91
+ * indicating a CBOR-encoded mDOC DeviceResponse.
92
+ */
93
+ canParse(vpToken: unknown): boolean;
94
+ /**
95
+ * Parses and validates an mDOC DeviceResponse token.
96
+ *
97
+ * @throws {MalformedCredentialError} when the CBOR structure cannot be decoded
98
+ * or does not match the expected DeviceResponse format
99
+ */
100
+ parse(vpToken: unknown, options: ParseOptions): Promise<PresentationResult>;
101
+ }
102
+
103
+ declare class InvalidSignatureError extends Error {
104
+ constructor(message?: string);
105
+ }
106
+ declare class ExpiredCredentialError extends Error {
107
+ constructor(message?: string);
108
+ }
109
+ declare class UnsupportedFormatError extends Error {
110
+ constructor(format: string);
111
+ }
112
+ declare class MalformedCredentialError extends Error {
113
+ constructor(message?: string);
114
+ }
115
+ declare class NonceValidationError extends Error {
116
+ constructor(message?: string);
117
+ }
118
+
119
+ declare function createAuthorizationRequest(input: AuthorizationRequestInput): AuthorizationRequest;
120
+
121
+ declare function parsePresentation(vpToken: unknown, options: ParseOptions): Promise<PresentationResult>;
122
+
123
+ declare const VERSION = "0.1.0";
124
+
125
+ export { type AuthorizationRequest, type AuthorizationRequestInput, type CredentialClaims, type CredentialFormat, ExpiredCredentialError, type ICredentialParser, InvalidSignatureError, type IssuerInfo, MalformedCredentialError, MdocParser, NonceValidationError, type ParseOptions, type PresentationResult, SdJwtParser, UnsupportedFormatError, VERSION, createAuthorizationRequest, parsePresentation };
package/dist/index.js ADDED
@@ -0,0 +1,447 @@
1
+ import { splitSdJwt, decodeJwt, decodeSdJwt, getClaims } from '@sd-jwt/decode';
2
+ import { decode } from 'cbor-x';
3
+ import { v4 } from 'uuid';
4
+
5
+ // src/parsers/sd-jwt.parser.ts
6
+
7
+ // src/errors.ts
8
+ var InvalidSignatureError = class extends Error {
9
+ constructor(message = "Credential signature validation failed") {
10
+ super(message);
11
+ this.name = "InvalidSignatureError";
12
+ Object.setPrototypeOf(this, new.target.prototype);
13
+ }
14
+ };
15
+ var ExpiredCredentialError = class extends Error {
16
+ constructor(message = "Credential has expired") {
17
+ super(message);
18
+ this.name = "ExpiredCredentialError";
19
+ Object.setPrototypeOf(this, new.target.prototype);
20
+ }
21
+ };
22
+ var UnsupportedFormatError = class extends Error {
23
+ constructor(format) {
24
+ super(`Unsupported credential format: ${format}`);
25
+ this.name = "UnsupportedFormatError";
26
+ Object.setPrototypeOf(this, new.target.prototype);
27
+ }
28
+ };
29
+ var MalformedCredentialError = class extends Error {
30
+ constructor(message = "Credential structure is malformed") {
31
+ super(message);
32
+ this.name = "MalformedCredentialError";
33
+ Object.setPrototypeOf(this, new.target.prototype);
34
+ }
35
+ };
36
+ var NonceValidationError = class extends Error {
37
+ constructor(message = "Nonce does not match expected value") {
38
+ super(message);
39
+ this.name = "NonceValidationError";
40
+ Object.setPrototypeOf(this, new.target.prototype);
41
+ }
42
+ };
43
+
44
+ // src/parsers/sd-jwt.parser.ts
45
+ var sha256Hasher = async (data, _alg) => {
46
+ const encoder = new TextEncoder();
47
+ const input = typeof data === "string" ? encoder.encode(data) : data;
48
+ const hashBuffer = await crypto.subtle.digest("SHA-256", input);
49
+ return new Uint8Array(hashBuffer);
50
+ };
51
+ var BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
52
+ function base64ToBytes(base64) {
53
+ const cleaned = base64.replace(/=+$/, "");
54
+ const byteLength = Math.floor(cleaned.length * 3 / 4);
55
+ const buffer = new ArrayBuffer(byteLength);
56
+ const bytes = new Uint8Array(buffer);
57
+ let byteIndex = 0;
58
+ for (let i = 0; i < cleaned.length; i += 4) {
59
+ const a = BASE64_CHARS.indexOf(cleaned[i]);
60
+ const b = i + 1 < cleaned.length ? BASE64_CHARS.indexOf(cleaned[i + 1]) : 0;
61
+ const c = i + 2 < cleaned.length ? BASE64_CHARS.indexOf(cleaned[i + 2]) : 0;
62
+ const d = i + 3 < cleaned.length ? BASE64_CHARS.indexOf(cleaned[i + 3]) : 0;
63
+ bytes[byteIndex++] = a << 2 | b >> 4;
64
+ if (byteIndex < byteLength) bytes[byteIndex++] = (b & 15) << 4 | c >> 2;
65
+ if (byteIndex < byteLength) bytes[byteIndex++] = (c & 3) << 6 | d;
66
+ }
67
+ return bytes;
68
+ }
69
+ function extractCertificateFromX5c(x5cEntry) {
70
+ return base64ToBytes(x5cEntry);
71
+ }
72
+ function bytesEqual(a, b) {
73
+ if (a.length !== b.length) return false;
74
+ for (let i = 0; i < a.length; i++) {
75
+ if (a[i] !== b[i]) return false;
76
+ }
77
+ return true;
78
+ }
79
+ function mapToCredentialClaims(raw) {
80
+ const claims = {};
81
+ if (typeof raw["age_over_18"] === "boolean") {
82
+ claims.age_over_18 = raw["age_over_18"];
83
+ }
84
+ if (typeof raw["age_over_21"] === "boolean") {
85
+ claims.age_over_21 = raw["age_over_21"];
86
+ }
87
+ if (typeof raw["resident_country"] === "string") {
88
+ claims.resident_country = raw["resident_country"];
89
+ }
90
+ if (typeof raw["nationality"] === "string") {
91
+ claims.nationality = raw["nationality"];
92
+ }
93
+ if (typeof raw["family_name_birth"] === "string") {
94
+ claims.family_name_birth = raw["family_name_birth"];
95
+ }
96
+ for (const [key, value] of Object.entries(raw)) {
97
+ if (!(key in claims) && !key.startsWith("_sd") && key !== "iss" && key !== "exp" && key !== "iat" && key !== "cnf" && key !== "vct") {
98
+ claims[key] = value;
99
+ }
100
+ }
101
+ return claims;
102
+ }
103
+ var SdJwtParser = class {
104
+ format = "sd-jwt-vc";
105
+ /**
106
+ * Returns true when the token looks like an SD-JWT:
107
+ * it must be a string containing the `~` disclosure separator.
108
+ */
109
+ canParse(vpToken) {
110
+ return typeof vpToken === "string" && vpToken.includes("~");
111
+ }
112
+ /**
113
+ * Parses and validates an SD-JWT VC token.
114
+ *
115
+ * @throws {MalformedCredentialError} when the token structure cannot be decoded
116
+ */
117
+ async parse(vpToken, options) {
118
+ if (typeof vpToken !== "string") {
119
+ throw new MalformedCredentialError("VP token must be a string");
120
+ }
121
+ const invalidResult = (error) => ({
122
+ valid: false,
123
+ format: this.format,
124
+ claims: {},
125
+ issuer: { certificate: new Uint8Array(), country: "" },
126
+ error
127
+ });
128
+ let parts;
129
+ try {
130
+ parts = splitSdJwt(vpToken);
131
+ } catch {
132
+ throw new MalformedCredentialError("Failed to split SD-JWT structure");
133
+ }
134
+ let header;
135
+ let payload;
136
+ try {
137
+ const jwt = decodeJwt(parts.jwt);
138
+ header = jwt.header;
139
+ payload = jwt.payload;
140
+ } catch {
141
+ throw new MalformedCredentialError("Failed to decode SD-JWT issuer JWT");
142
+ }
143
+ const x5cArray = header["x5c"];
144
+ let issuerCertBytes = new Uint8Array();
145
+ let issuerInfo = { certificate: new Uint8Array(), country: "" };
146
+ if (Array.isArray(x5cArray) && x5cArray.length > 0 && typeof x5cArray[0] === "string") {
147
+ try {
148
+ issuerCertBytes = extractCertificateFromX5c(x5cArray[0]);
149
+ const issuerString = typeof payload["iss"] === "string" ? payload["iss"] : "";
150
+ issuerInfo = {
151
+ certificate: issuerCertBytes,
152
+ country: extractCountryHint(issuerString)
153
+ };
154
+ } catch {
155
+ return invalidResult("Failed to extract issuer certificate from x5c");
156
+ }
157
+ }
158
+ if (options.trustedCertificates.length > 0) {
159
+ const isTrusted = options.trustedCertificates.some((trusted) => bytesEqual(trusted, issuerCertBytes));
160
+ if (!isTrusted) {
161
+ return invalidResult("Issuer certificate is not trusted");
162
+ }
163
+ } else if (issuerCertBytes.length === 0) {
164
+ return invalidResult("No issuer certificate found and no trusted certificates configured");
165
+ }
166
+ const exp = payload["exp"];
167
+ if (typeof exp === "number") {
168
+ const nowSeconds = Math.floor(Date.now() / 1e3);
169
+ if (exp < nowSeconds) {
170
+ return invalidResult("Credential has expired");
171
+ }
172
+ }
173
+ if (parts.kbJwt) {
174
+ try {
175
+ const kbDecoded = decodeJwt(parts.kbJwt);
176
+ const kbPayload = kbDecoded.payload;
177
+ const kbNonce = kbPayload["nonce"];
178
+ if (typeof kbNonce === "string" && kbNonce !== options.nonce) {
179
+ return invalidResult("Key binding JWT nonce does not match");
180
+ }
181
+ } catch {
182
+ return invalidResult("Failed to decode key binding JWT");
183
+ }
184
+ }
185
+ let resolvedClaims;
186
+ try {
187
+ const decoded = await decodeSdJwt(vpToken, sha256Hasher);
188
+ resolvedClaims = await getClaims(
189
+ decoded.jwt.payload,
190
+ decoded.disclosures,
191
+ sha256Hasher
192
+ );
193
+ } catch {
194
+ resolvedClaims = payload;
195
+ }
196
+ const claims = mapToCredentialClaims(resolvedClaims);
197
+ return {
198
+ valid: true,
199
+ format: this.format,
200
+ claims,
201
+ issuer: issuerInfo
202
+ };
203
+ }
204
+ };
205
+ function extractCountryHint(issuer) {
206
+ try {
207
+ const url = new URL(issuer);
208
+ const tld = url.hostname.split(".").pop()?.toUpperCase();
209
+ if (tld && tld.length === 2) {
210
+ return tld;
211
+ }
212
+ } catch {
213
+ }
214
+ return "";
215
+ }
216
+ function isBinaryData(value) {
217
+ return value instanceof Uint8Array;
218
+ }
219
+ function bytesEqual2(a, b) {
220
+ if (a.length !== b.length) return false;
221
+ for (let i = 0; i < a.length; i++) {
222
+ if (a[i] !== b[i]) return false;
223
+ }
224
+ return true;
225
+ }
226
+ function extractCertificateFromIssuerAuth(issuerAuth) {
227
+ const unprotected = issuerAuth[1];
228
+ if (unprotected && typeof unprotected === "object") {
229
+ const certFromUnprotected = extractX5Chain(unprotected);
230
+ if (certFromUnprotected) return certFromUnprotected;
231
+ }
232
+ const protectedHeader = issuerAuth[0];
233
+ if (isBinaryData(protectedHeader)) {
234
+ try {
235
+ const decoded = decode(protectedHeader);
236
+ if (decoded && typeof decoded === "object") {
237
+ const certFromProtected = extractX5Chain(decoded);
238
+ if (certFromProtected) return certFromProtected;
239
+ }
240
+ } catch {
241
+ }
242
+ return new Uint8Array(protectedHeader);
243
+ }
244
+ return new Uint8Array();
245
+ }
246
+ function extractX5Chain(header) {
247
+ if (header instanceof Map) {
248
+ const cert = header.get(33);
249
+ if (isBinaryData(cert)) {
250
+ return new Uint8Array(cert);
251
+ }
252
+ } else if (typeof header === "object" && header !== null) {
253
+ const rec = header;
254
+ const cert = rec[33] ?? rec["x5chain"] ?? rec["33"];
255
+ if (isBinaryData(cert)) {
256
+ return new Uint8Array(cert);
257
+ }
258
+ }
259
+ return null;
260
+ }
261
+ function mapToCredentialClaims2(items) {
262
+ const claims = {};
263
+ for (const item of items) {
264
+ const key = item.elementIdentifier;
265
+ const value = item.elementValue;
266
+ switch (key) {
267
+ case "age_over_18":
268
+ if (typeof value === "boolean") claims.age_over_18 = value;
269
+ break;
270
+ case "age_over_21":
271
+ if (typeof value === "boolean") claims.age_over_21 = value;
272
+ break;
273
+ case "resident_country":
274
+ if (typeof value === "string") claims.resident_country = value;
275
+ break;
276
+ case "nationality":
277
+ if (typeof value === "string") claims.nationality = value;
278
+ break;
279
+ case "family_name_birth":
280
+ if (typeof value === "string") claims.family_name_birth = value;
281
+ break;
282
+ default:
283
+ claims[key] = value;
284
+ break;
285
+ }
286
+ }
287
+ return claims;
288
+ }
289
+ function extractValidityPeriod(issuerAuth) {
290
+ const payload = issuerAuth[2];
291
+ if (!payload) return {};
292
+ try {
293
+ const decoded = isBinaryData(payload) ? decode(payload) : payload;
294
+ if (decoded && typeof decoded === "object") {
295
+ const obj = decoded;
296
+ const validityInfo = obj["validityInfo"];
297
+ if (validityInfo) {
298
+ return {
299
+ validFrom: validityInfo["validFrom"] instanceof Date ? validityInfo["validFrom"] : typeof validityInfo["validFrom"] === "string" ? new Date(validityInfo["validFrom"]) : void 0,
300
+ validUntil: validityInfo["validUntil"] instanceof Date ? validityInfo["validUntil"] : typeof validityInfo["validUntil"] === "string" ? new Date(validityInfo["validUntil"]) : void 0
301
+ };
302
+ }
303
+ }
304
+ } catch {
305
+ }
306
+ return {};
307
+ }
308
+ var MdocParser = class {
309
+ format = "mdoc";
310
+ /**
311
+ * Returns true when the token is binary data (Uint8Array or Buffer),
312
+ * indicating a CBOR-encoded mDOC DeviceResponse.
313
+ */
314
+ canParse(vpToken) {
315
+ return isBinaryData(vpToken);
316
+ }
317
+ /**
318
+ * Parses and validates an mDOC DeviceResponse token.
319
+ *
320
+ * @throws {MalformedCredentialError} when the CBOR structure cannot be decoded
321
+ * or does not match the expected DeviceResponse format
322
+ */
323
+ async parse(vpToken, options) {
324
+ if (!isBinaryData(vpToken)) {
325
+ throw new MalformedCredentialError("VP token must be a Uint8Array or Buffer");
326
+ }
327
+ const invalidResult = (error) => ({
328
+ valid: false,
329
+ format: this.format,
330
+ claims: {},
331
+ issuer: { certificate: new Uint8Array(), country: "" },
332
+ error
333
+ });
334
+ let deviceResponse;
335
+ try {
336
+ deviceResponse = decode(vpToken);
337
+ } catch {
338
+ throw new MalformedCredentialError("Failed to decode CBOR DeviceResponse");
339
+ }
340
+ if (!deviceResponse || typeof deviceResponse !== "object" || !Array.isArray(deviceResponse.documents) || deviceResponse.documents.length === 0) {
341
+ throw new MalformedCredentialError("Invalid DeviceResponse structure: missing or empty documents array");
342
+ }
343
+ const document = deviceResponse.documents[0];
344
+ if (!document.issuerSigned || !document.issuerSigned.nameSpaces || !document.issuerSigned.issuerAuth) {
345
+ throw new MalformedCredentialError("Invalid mDOC document: missing issuerSigned data");
346
+ }
347
+ const issuerAuth = document.issuerSigned.issuerAuth;
348
+ if (!Array.isArray(issuerAuth) || issuerAuth.length < 4) {
349
+ throw new MalformedCredentialError("Invalid issuerAuth: expected COSE_Sign1 array with 4 elements");
350
+ }
351
+ const issuerCertBytes = extractCertificateFromIssuerAuth(issuerAuth);
352
+ const issuerInfo = {
353
+ certificate: issuerCertBytes,
354
+ country: ""
355
+ };
356
+ if (options.trustedCertificates.length > 0) {
357
+ const isTrusted = options.trustedCertificates.some((trusted) => bytesEqual2(trusted, issuerCertBytes));
358
+ if (!isTrusted) {
359
+ return invalidResult("Issuer certificate is not trusted");
360
+ }
361
+ } else if (issuerCertBytes.length === 0) {
362
+ return invalidResult("No issuer certificate found and no trusted certificates configured");
363
+ }
364
+ const { validUntil } = extractValidityPeriod(issuerAuth);
365
+ if (validUntil) {
366
+ const now = /* @__PURE__ */ new Date();
367
+ if (validUntil < now) {
368
+ return invalidResult("Credential has expired");
369
+ }
370
+ }
371
+ const pidNamespace = "eu.europa.ec.eudi.pid.1";
372
+ const nameSpaces = document.issuerSigned.nameSpaces;
373
+ const items = nameSpaces[pidNamespace] ?? nameSpaces[document.docType] ?? Object.values(nameSpaces)[0] ?? [];
374
+ const claims = mapToCredentialClaims2(items);
375
+ if (claims.resident_country && typeof claims.resident_country === "string") {
376
+ issuerInfo.country = claims.resident_country;
377
+ }
378
+ return {
379
+ valid: true,
380
+ format: this.format,
381
+ claims,
382
+ issuer: issuerInfo
383
+ };
384
+ }
385
+ };
386
+ function createAuthorizationRequest(input) {
387
+ const state = input.state ?? v4();
388
+ const presentationDefinition = {
389
+ id: v4(),
390
+ input_descriptors: input.requestedAttributes.map((attr, index) => ({
391
+ id: `descriptor_${index}`,
392
+ format: Object.fromEntries(
393
+ input.acceptedFormats.map((fmt) => {
394
+ if (fmt === "sd-jwt-vc") return ["vc+sd-jwt", { "sd-jwt_alg_values": ["ES256"] }];
395
+ return ["mso_mdoc", { alg: ["ES256"] }];
396
+ })
397
+ ),
398
+ constraints: {
399
+ fields: [
400
+ {
401
+ path: [`$.${attr}`, `$['${attr}']`],
402
+ filter: { type: attr.startsWith("age_over") ? "boolean" : "string" }
403
+ }
404
+ ]
405
+ }
406
+ }))
407
+ };
408
+ const params = new URLSearchParams({
409
+ response_type: "vp_token",
410
+ response_mode: "direct_post",
411
+ response_uri: input.responseUri,
412
+ client_id: input.clientId,
413
+ nonce: input.nonce,
414
+ state,
415
+ presentation_definition: JSON.stringify(presentationDefinition)
416
+ });
417
+ return {
418
+ uri: `openid4vp://authorize?${params.toString()}`,
419
+ presentationDefinition,
420
+ nonce: input.nonce,
421
+ state
422
+ };
423
+ }
424
+
425
+ // src/presentation.ts
426
+ var parsers = [new SdJwtParser(), new MdocParser()];
427
+ async function parsePresentation(vpToken, options) {
428
+ for (const parser of parsers) {
429
+ if (parser.canParse(vpToken)) {
430
+ return parser.parse(vpToken, options);
431
+ }
432
+ }
433
+ return {
434
+ valid: false,
435
+ format: "sd-jwt-vc",
436
+ claims: {},
437
+ issuer: { certificate: new Uint8Array(), country: "" },
438
+ error: "Unsupported credential format"
439
+ };
440
+ }
441
+
442
+ // src/index.ts
443
+ var VERSION = "0.1.0";
444
+
445
+ export { ExpiredCredentialError, InvalidSignatureError, MalformedCredentialError, MdocParser, NonceValidationError, SdJwtParser, UnsupportedFormatError, VERSION, createAuthorizationRequest, parsePresentation };
446
+ //# sourceMappingURL=index.js.map
447
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/parsers/sd-jwt.parser.ts","../src/parsers/mdoc.parser.ts","../src/authorization.ts","../src/presentation.ts","../src/index.ts"],"names":["bytesEqual","mapToCredentialClaims","uuidv4"],"mappings":"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","file":"index.js","sourcesContent":["export class InvalidSignatureError extends Error {\n constructor(message = 'Credential signature validation failed') {\n super(message);\n this.name = 'InvalidSignatureError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class ExpiredCredentialError extends Error {\n constructor(message = 'Credential has expired') {\n super(message);\n this.name = 'ExpiredCredentialError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class UnsupportedFormatError extends Error {\n constructor(format: string) {\n super(`Unsupported credential format: ${format}`);\n this.name = 'UnsupportedFormatError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class MalformedCredentialError extends Error {\n constructor(message = 'Credential structure is malformed') {\n super(message);\n this.name = 'MalformedCredentialError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class NonceValidationError extends Error {\n constructor(message = 'Nonce does not match expected value') {\n super(message);\n this.name = 'NonceValidationError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { splitSdJwt, decodeJwt, decodeSdJwt, getClaims } from '@sd-jwt/decode';\n\nimport { MalformedCredentialError } from '../errors.js';\nimport type { IssuerInfo } from '../types/issuer.js';\nimport type { CredentialFormat, CredentialClaims, PresentationResult } from '../types/presentation.js';\n\nimport type { ICredentialParser, ParseOptions } from './parser.interface.js';\n\n/**\n * Hasher function signature expected by @sd-jwt/decode.\n * Inlined here to avoid a direct dependency on @sd-jwt/types.\n */\ntype SdJwtHasher = (data: string | ArrayBuffer, alg: string) => Promise<Uint8Array>;\n\n/**\n * SHA-256 hasher implementation using the Web Crypto API.\n * Compatible with the @sd-jwt/decode Hasher signature.\n */\nconst sha256Hasher: SdJwtHasher = async (data: string | ArrayBuffer, _alg: string): Promise<Uint8Array> => {\n const encoder = new TextEncoder();\n const input = typeof data === 'string' ? encoder.encode(data) : data;\n const hashBuffer = await crypto.subtle.digest('SHA-256', input);\n return new Uint8Array(hashBuffer);\n};\n\n// Standard base64 alphabet for decoding x5c certificate entries\nconst BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\n/**\n * Decodes a standard base64-encoded string into raw bytes.\n * Does not depend on Node.js Buffer or browser atob.\n */\nfunction base64ToBytes(base64: string): Uint8Array<ArrayBuffer> {\n // Strip any padding characters\n const cleaned = base64.replace(/=+$/, '');\n const byteLength = Math.floor((cleaned.length * 3) / 4);\n const buffer = new ArrayBuffer(byteLength);\n const bytes = new Uint8Array(buffer);\n\n let byteIndex = 0;\n for (let i = 0; i < cleaned.length; i += 4) {\n const a = BASE64_CHARS.indexOf(cleaned[i]);\n const b = i + 1 < cleaned.length ? BASE64_CHARS.indexOf(cleaned[i + 1]) : 0;\n const c = i + 2 < cleaned.length ? BASE64_CHARS.indexOf(cleaned[i + 2]) : 0;\n const d = i + 3 < cleaned.length ? BASE64_CHARS.indexOf(cleaned[i + 3]) : 0;\n\n bytes[byteIndex++] = (a << 2) | (b >> 4);\n if (byteIndex < byteLength) bytes[byteIndex++] = ((b & 0x0f) << 4) | (c >> 2);\n if (byteIndex < byteLength) bytes[byteIndex++] = ((c & 0x03) << 6) | d;\n }\n\n return bytes;\n}\n\n/**\n * Extracts the DER-encoded certificate bytes from a base64-encoded X.509 certificate\n * found in the JWT header's x5c field.\n */\nfunction extractCertificateFromX5c(x5cEntry: string): Uint8Array<ArrayBuffer> {\n return base64ToBytes(x5cEntry);\n}\n\n/**\n * Checks whether two Uint8Array instances contain identical bytes.\n */\nfunction bytesEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * Maps raw decoded claims to the CredentialClaims structure,\n * picking known age/residency fields and preserving any extra keys.\n */\nfunction mapToCredentialClaims(raw: Record<string, unknown>): CredentialClaims {\n const claims: CredentialClaims = {};\n\n if (typeof raw['age_over_18'] === 'boolean') {\n claims.age_over_18 = raw['age_over_18'];\n }\n if (typeof raw['age_over_21'] === 'boolean') {\n claims.age_over_21 = raw['age_over_21'];\n }\n if (typeof raw['resident_country'] === 'string') {\n claims.resident_country = raw['resident_country'];\n }\n if (typeof raw['nationality'] === 'string') {\n claims.nationality = raw['nationality'];\n }\n if (typeof raw['family_name_birth'] === 'string') {\n claims.family_name_birth = raw['family_name_birth'];\n }\n\n // Preserve any additional claim keys not already mapped\n for (const [key, value] of Object.entries(raw)) {\n if (\n !(key in claims) &&\n !key.startsWith('_sd') &&\n key !== 'iss' &&\n key !== 'exp' &&\n key !== 'iat' &&\n key !== 'cnf' &&\n key !== 'vct'\n ) {\n claims[key] = value;\n }\n }\n\n return claims;\n}\n\n/**\n * SD-JWT VC credential parser.\n *\n * Decodes and validates SD-JWT Verifiable Credentials as specified in\n * the EUDI Wallet ecosystem. Performs:\n * - Structure validation (JWT~disclosure~kb format)\n * - Issuer certificate trust chain verification\n * - Expiry checking\n * - Nonce validation against the key binding JWT\n * - Selective disclosure claim extraction\n */\nexport class SdJwtParser implements ICredentialParser {\n readonly format: CredentialFormat = 'sd-jwt-vc';\n\n /**\n * Returns true when the token looks like an SD-JWT:\n * it must be a string containing the `~` disclosure separator.\n */\n canParse(vpToken: unknown): boolean {\n return typeof vpToken === 'string' && vpToken.includes('~');\n }\n\n /**\n * Parses and validates an SD-JWT VC token.\n *\n * @throws {MalformedCredentialError} when the token structure cannot be decoded\n */\n async parse(vpToken: unknown, options: ParseOptions): Promise<PresentationResult> {\n if (typeof vpToken !== 'string') {\n throw new MalformedCredentialError('VP token must be a string');\n }\n\n const invalidResult = (error: string): PresentationResult => ({\n valid: false,\n format: this.format,\n claims: {},\n issuer: { certificate: new Uint8Array(), country: '' },\n error,\n });\n\n // Step 1: Split the SD-JWT into its components\n let parts: ReturnType<typeof splitSdJwt>;\n try {\n parts = splitSdJwt(vpToken);\n } catch {\n throw new MalformedCredentialError('Failed to split SD-JWT structure');\n }\n\n // Step 2: Decode the issuer JWT header and payload\n let header: Record<string, unknown>;\n let payload: Record<string, unknown>;\n try {\n const jwt = decodeJwt(parts.jwt);\n header = jwt.header;\n payload = jwt.payload;\n } catch {\n throw new MalformedCredentialError('Failed to decode SD-JWT issuer JWT');\n }\n\n // Step 3: Extract issuer certificate from x5c header\n const x5cArray = header['x5c'];\n let issuerCertBytes = new Uint8Array();\n let issuerInfo: IssuerInfo = { certificate: new Uint8Array(), country: '' };\n\n if (Array.isArray(x5cArray) && x5cArray.length > 0 && typeof x5cArray[0] === 'string') {\n try {\n issuerCertBytes = extractCertificateFromX5c(x5cArray[0]);\n // Attempt to derive the country from the certificate's issuer string\n // In production, this would parse the X.509 subject, but for now we\n // fall back to payload-level hints.\n const issuerString = typeof payload['iss'] === 'string' ? payload['iss'] : '';\n issuerInfo = {\n certificate: issuerCertBytes,\n country: extractCountryHint(issuerString),\n };\n } catch {\n return invalidResult('Failed to extract issuer certificate from x5c');\n }\n }\n\n // Step 4: Verify trust — certificate must be in the trusted set\n if (options.trustedCertificates.length > 0) {\n const isTrusted = options.trustedCertificates.some((trusted) => bytesEqual(trusted, issuerCertBytes));\n if (!isTrusted) {\n return invalidResult('Issuer certificate is not trusted');\n }\n } else if (issuerCertBytes.length === 0) {\n return invalidResult('No issuer certificate found and no trusted certificates configured');\n }\n\n // Step 5: Check expiry\n const exp = payload['exp'];\n if (typeof exp === 'number') {\n const nowSeconds = Math.floor(Date.now() / 1000);\n if (exp < nowSeconds) {\n return invalidResult('Credential has expired');\n }\n }\n\n // Step 6: Validate nonce in key binding JWT\n if (parts.kbJwt) {\n try {\n const kbDecoded = decodeJwt(parts.kbJwt);\n const kbPayload = kbDecoded.payload as Record<string, unknown>;\n const kbNonce = kbPayload['nonce'];\n if (typeof kbNonce === 'string' && kbNonce !== options.nonce) {\n return invalidResult('Key binding JWT nonce does not match');\n }\n } catch {\n return invalidResult('Failed to decode key binding JWT');\n }\n }\n\n // Step 7: Decode disclosures and extract claims\n let resolvedClaims: Record<string, unknown>;\n try {\n const decoded = await decodeSdJwt(vpToken, sha256Hasher);\n resolvedClaims = await getClaims<Record<string, unknown>>(\n decoded.jwt.payload,\n decoded.disclosures,\n sha256Hasher\n );\n } catch {\n // If SD-JWT decoding fails (e.g., no _sd_alg), fall back to raw payload\n resolvedClaims = payload;\n }\n\n const claims = mapToCredentialClaims(resolvedClaims);\n\n return {\n valid: true,\n format: this.format,\n claims,\n issuer: issuerInfo,\n };\n }\n}\n\n/**\n * Best-effort extraction of a country code hint from an issuer URL or string.\n * For example, \"https://issuer.de/...\" would yield \"DE\".\n */\nfunction extractCountryHint(issuer: string): string {\n try {\n const url = new URL(issuer);\n const tld = url.hostname.split('.').pop()?.toUpperCase();\n if (tld && tld.length === 2) {\n return tld;\n }\n } catch {\n // Not a valid URL — ignore\n }\n return '';\n}\n","import { decode } from 'cbor-x';\n\nimport { MalformedCredentialError } from '../errors.js';\nimport type { IssuerInfo } from '../types/issuer.js';\nimport type { CredentialFormat, CredentialClaims, PresentationResult } from '../types/presentation.js';\n\nimport type { ICredentialParser, ParseOptions } from './parser.interface.js';\n\n/**\n * Runtime check for binary data. Returns true for Uint8Array and Node.js Buffer\n * without referencing the Buffer type (which is unavailable in DOM-only tsconfig).\n * Buffer extends Uint8Array, so `instanceof Uint8Array` catches both.\n */\nfunction isBinaryData(value: unknown): value is Uint8Array {\n return value instanceof Uint8Array;\n}\n\n/**\n * Checks whether two Uint8Array instances contain identical bytes.\n */\nfunction bytesEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * Represents a single IssuerSignedItem element from an mDOC nameSpace.\n * ISO 18013-5 Section 8.3.2.1.2.\n */\ninterface IssuerSignedItem {\n digestID: number;\n random: Uint8Array | unknown;\n elementIdentifier: string;\n elementValue: unknown;\n}\n\n/**\n * Simplified DeviceResponse structure from ISO 18013-5.\n * Only the fields required for claim extraction and trust validation.\n */\ninterface MdocDocument {\n docType: string;\n issuerSigned: {\n nameSpaces: Record<string, IssuerSignedItem[]>;\n issuerAuth: unknown[];\n };\n}\n\ninterface DeviceResponse {\n version: string;\n documents: MdocDocument[];\n status: number;\n}\n\n/**\n * Extracts the issuer certificate bytes from a COSE_Sign1 issuerAuth structure.\n *\n * The issuerAuth is a COSE_Sign1 array: [protected, unprotected, payload, signature].\n * The protected header (index 0) is CBOR-encoded and may contain the x5chain (label 33)\n * with the issuer certificate. The unprotected header (index 1) may also carry it.\n *\n * If neither header contains a certificate, the raw protected bytes are returned\n * as a fallback for trust matching (test/simplified scenarios).\n */\nfunction extractCertificateFromIssuerAuth(issuerAuth: unknown[]): Uint8Array {\n // Try unprotected header first (index 1) -- it's already decoded as a Map or object\n const unprotected = issuerAuth[1];\n if (unprotected && typeof unprotected === 'object') {\n // x5chain is COSE header label 33\n const certFromUnprotected = extractX5Chain(unprotected);\n if (certFromUnprotected) return certFromUnprotected;\n }\n\n // Try protected header (index 0) -- CBOR-encoded bytes\n const protectedHeader = issuerAuth[0];\n if (isBinaryData(protectedHeader)) {\n try {\n const decoded = decode(protectedHeader) as unknown;\n if (decoded && typeof decoded === 'object') {\n const certFromProtected = extractX5Chain(decoded);\n if (certFromProtected) return certFromProtected;\n }\n } catch {\n // Protected header could not be decoded -- fall through\n }\n // Return raw protected bytes as fallback identifier\n return new Uint8Array(protectedHeader);\n }\n\n return new Uint8Array();\n}\n\n/**\n * Extracts certificate bytes from an x5chain (label 33) in a COSE header.\n * The header can be a plain object or a Map.\n */\nfunction extractX5Chain(header: unknown): Uint8Array | null {\n if (header instanceof Map) {\n const cert = header.get(33) as unknown;\n if (isBinaryData(cert)) {\n return new Uint8Array(cert);\n }\n } else if (typeof header === 'object' && header !== null) {\n const rec = header as Record<string | number, unknown>;\n const cert = rec[33] ?? rec['x5chain'] ?? rec['33'];\n if (isBinaryData(cert)) {\n return new Uint8Array(cert);\n }\n }\n return null;\n}\n\n/**\n * Maps raw mDOC IssuerSignedItem elements to the CredentialClaims structure,\n * picking known age/residency fields and preserving any additional identifiers.\n */\nfunction mapToCredentialClaims(items: IssuerSignedItem[]): CredentialClaims {\n const claims: CredentialClaims = {};\n\n for (const item of items) {\n const key = item.elementIdentifier;\n const value = item.elementValue;\n\n switch (key) {\n case 'age_over_18':\n if (typeof value === 'boolean') claims.age_over_18 = value;\n break;\n case 'age_over_21':\n if (typeof value === 'boolean') claims.age_over_21 = value;\n break;\n case 'resident_country':\n if (typeof value === 'string') claims.resident_country = value;\n break;\n case 'nationality':\n if (typeof value === 'string') claims.nationality = value;\n break;\n case 'family_name_birth':\n if (typeof value === 'string') claims.family_name_birth = value;\n break;\n default:\n claims[key] = value;\n break;\n }\n }\n\n return claims;\n}\n\n/**\n * Extracts validity period from the issuerAuth payload.\n *\n * The payload (index 2) of the COSE_Sign1 is CBOR-encoded and should\n * contain a MobileSecurityObject with a validityInfo field carrying\n * `validFrom` and `validUntil` dates.\n */\nfunction extractValidityPeriod(issuerAuth: unknown[]): { validFrom?: Date; validUntil?: Date } {\n const payload = issuerAuth[2];\n if (!payload) return {};\n\n try {\n // payload can be raw CBOR bytes or already decoded\n const decoded = isBinaryData(payload) ? (decode(payload) as unknown) : payload;\n\n if (decoded && typeof decoded === 'object') {\n const obj = decoded as Record<string, unknown>;\n const validityInfo = obj['validityInfo'] as Record<string, unknown> | undefined;\n if (validityInfo) {\n return {\n validFrom:\n validityInfo['validFrom'] instanceof Date\n ? validityInfo['validFrom']\n : typeof validityInfo['validFrom'] === 'string'\n ? new Date(validityInfo['validFrom'])\n : undefined,\n validUntil:\n validityInfo['validUntil'] instanceof Date\n ? validityInfo['validUntil']\n : typeof validityInfo['validUntil'] === 'string'\n ? new Date(validityInfo['validUntil'])\n : undefined,\n };\n }\n }\n } catch {\n // Could not decode payload -- validity check will be skipped\n }\n\n return {};\n}\n\n/**\n * mDOC / mDL credential parser.\n *\n * Decodes and validates mDOC (Mobile Document) credentials as specified in\n * ISO 18013-5 and the EUDI Wallet ecosystem. Processes CBOR-encoded\n * DeviceResponse structures containing:\n * - Document type validation (eu.europa.ec.eudi.pid.1)\n * - Issuer certificate trust chain verification via COSE_Sign1 issuerAuth\n * - Validity period checking\n * - Claim extraction from IssuerSignedItem nameSpace elements\n */\nexport class MdocParser implements ICredentialParser {\n readonly format: CredentialFormat = 'mdoc';\n\n /**\n * Returns true when the token is binary data (Uint8Array or Buffer),\n * indicating a CBOR-encoded mDOC DeviceResponse.\n */\n canParse(vpToken: unknown): boolean {\n return isBinaryData(vpToken);\n }\n\n /**\n * Parses and validates an mDOC DeviceResponse token.\n *\n * @throws {MalformedCredentialError} when the CBOR structure cannot be decoded\n * or does not match the expected DeviceResponse format\n */\n async parse(vpToken: unknown, options: ParseOptions): Promise<PresentationResult> {\n if (!isBinaryData(vpToken)) {\n throw new MalformedCredentialError('VP token must be a Uint8Array or Buffer');\n }\n\n const invalidResult = (error: string): PresentationResult => ({\n valid: false,\n format: this.format,\n claims: {},\n issuer: { certificate: new Uint8Array(), country: '' },\n error,\n });\n\n // Step 1: Decode the CBOR DeviceResponse\n let deviceResponse: DeviceResponse;\n try {\n deviceResponse = decode(vpToken) as DeviceResponse;\n } catch {\n throw new MalformedCredentialError('Failed to decode CBOR DeviceResponse');\n }\n\n // Step 2: Validate basic DeviceResponse structure\n if (\n !deviceResponse ||\n typeof deviceResponse !== 'object' ||\n !Array.isArray(deviceResponse.documents) ||\n deviceResponse.documents.length === 0\n ) {\n throw new MalformedCredentialError('Invalid DeviceResponse structure: missing or empty documents array');\n }\n\n const document = deviceResponse.documents[0];\n if (!document.issuerSigned || !document.issuerSigned.nameSpaces || !document.issuerSigned.issuerAuth) {\n throw new MalformedCredentialError('Invalid mDOC document: missing issuerSigned data');\n }\n\n // Step 3: Extract issuer certificate from COSE_Sign1 issuerAuth\n const issuerAuth = document.issuerSigned.issuerAuth;\n if (!Array.isArray(issuerAuth) || issuerAuth.length < 4) {\n throw new MalformedCredentialError('Invalid issuerAuth: expected COSE_Sign1 array with 4 elements');\n }\n\n const issuerCertBytes = extractCertificateFromIssuerAuth(issuerAuth);\n const issuerInfo: IssuerInfo = {\n certificate: issuerCertBytes,\n country: '',\n };\n\n // Step 4: Verify trust -- certificate must be in the trusted set\n if (options.trustedCertificates.length > 0) {\n const isTrusted = options.trustedCertificates.some((trusted) => bytesEqual(trusted, issuerCertBytes));\n if (!isTrusted) {\n return invalidResult('Issuer certificate is not trusted');\n }\n } else if (issuerCertBytes.length === 0) {\n return invalidResult('No issuer certificate found and no trusted certificates configured');\n }\n\n // Step 5: Check validity period\n const { validUntil } = extractValidityPeriod(issuerAuth);\n if (validUntil) {\n const now = new Date();\n if (validUntil < now) {\n return invalidResult('Credential has expired');\n }\n }\n\n // Step 6: Extract claims from nameSpaces\n // Prefer the EUDI PID namespace, fall back to the first available namespace\n const pidNamespace = 'eu.europa.ec.eudi.pid.1';\n const nameSpaces = document.issuerSigned.nameSpaces;\n const items: IssuerSignedItem[] =\n nameSpaces[pidNamespace] ?? nameSpaces[document.docType] ?? Object.values(nameSpaces)[0] ?? [];\n\n const claims = mapToCredentialClaims(items);\n\n // Try to derive country from resident_country claim\n if (claims.resident_country && typeof claims.resident_country === 'string') {\n issuerInfo.country = claims.resident_country;\n }\n\n return {\n valid: true,\n format: this.format,\n claims,\n issuer: issuerInfo,\n };\n }\n}\n","import { v4 as uuidv4 } from 'uuid';\n\nimport type { AuthorizationRequestInput, AuthorizationRequest } from './types/authorization.js';\n\nexport function createAuthorizationRequest(input: AuthorizationRequestInput): AuthorizationRequest {\n const state = input.state ?? uuidv4();\n\n const presentationDefinition = {\n id: uuidv4(),\n input_descriptors: input.requestedAttributes.map((attr, index) => ({\n id: `descriptor_${index}`,\n format: Object.fromEntries(\n input.acceptedFormats.map((fmt) => {\n if (fmt === 'sd-jwt-vc') return ['vc+sd-jwt', { 'sd-jwt_alg_values': ['ES256'] }];\n return ['mso_mdoc', { alg: ['ES256'] }];\n })\n ),\n constraints: {\n fields: [\n {\n path: [`$.${attr}`, `$['${attr}']`],\n filter: { type: attr.startsWith('age_over') ? 'boolean' : 'string' },\n },\n ],\n },\n })),\n };\n\n const params = new URLSearchParams({\n response_type: 'vp_token',\n response_mode: 'direct_post',\n response_uri: input.responseUri,\n client_id: input.clientId,\n nonce: input.nonce,\n state,\n presentation_definition: JSON.stringify(presentationDefinition),\n });\n\n return {\n uri: `openid4vp://authorize?${params.toString()}`,\n presentationDefinition,\n nonce: input.nonce,\n state,\n };\n}\n","import { MdocParser } from './parsers/mdoc.parser.js';\nimport type { ParseOptions, ICredentialParser } from './parsers/parser.interface.js';\nimport { SdJwtParser } from './parsers/sd-jwt.parser.js';\nimport type { PresentationResult } from './types/presentation.js';\n\nconst parsers: ICredentialParser[] = [new SdJwtParser(), new MdocParser()];\n\nexport async function parsePresentation(vpToken: unknown, options: ParseOptions): Promise<PresentationResult> {\n for (const parser of parsers) {\n if (parser.canParse(vpToken)) {\n return parser.parse(vpToken, options);\n }\n }\n\n return {\n valid: false,\n format: 'sd-jwt-vc',\n claims: {},\n issuer: { certificate: new Uint8Array(), country: '' },\n error: 'Unsupported credential format',\n };\n}\n\nexport type { ParseOptions } from './parsers/parser.interface.js';\n","export const VERSION = '0.1.0';\n\nexport type {\n CredentialFormat,\n CredentialClaims,\n PresentationResult,\n IssuerInfo,\n AuthorizationRequestInput,\n AuthorizationRequest,\n} from './types/index.js';\n\nexport type { ICredentialParser, ParseOptions } from './parsers/parser.interface.js';\n\nexport { SdJwtParser } from './parsers/sd-jwt.parser.js';\nexport { MdocParser } from './parsers/mdoc.parser.js';\n\nexport {\n InvalidSignatureError,\n ExpiredCredentialError,\n UnsupportedFormatError,\n MalformedCredentialError,\n NonceValidationError,\n} from './errors.js';\n\nexport { createAuthorizationRequest } from './authorization.js';\n\nexport { parsePresentation } from './presentation.js';\n"]}