@openeudi/openid4vp 0.1.1 → 0.2.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.
- package/dist/index.cjs +103 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -15
- package/dist/index.d.ts +9 -15
- package/dist/index.js +104 -60
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var decode = require('@sd-jwt/decode');
|
|
4
|
+
var jose = require('jose');
|
|
4
5
|
var cborX = require('cbor-x');
|
|
5
6
|
var uuid = require('uuid');
|
|
6
7
|
|
|
@@ -44,6 +45,7 @@ var NonceValidationError = class extends Error {
|
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
// src/parsers/sd-jwt.parser.ts
|
|
48
|
+
var DEFAULT_ALLOWED_ALGORITHMS = ["ES256", "ES384", "ES512"];
|
|
47
49
|
var sha256Hasher = async (data, _alg) => {
|
|
48
50
|
const encoder = new TextEncoder();
|
|
49
51
|
const input = typeof data === "string" ? encoder.encode(data) : data;
|
|
@@ -68,9 +70,6 @@ function base64ToBytes(base64) {
|
|
|
68
70
|
}
|
|
69
71
|
return bytes;
|
|
70
72
|
}
|
|
71
|
-
function extractCertificateFromX5c(x5cEntry) {
|
|
72
|
-
return base64ToBytes(x5cEntry);
|
|
73
|
-
}
|
|
74
73
|
function bytesEqual(a, b) {
|
|
75
74
|
if (a.length !== b.length) return false;
|
|
76
75
|
for (let i = 0; i < a.length; i++) {
|
|
@@ -78,6 +77,19 @@ function bytesEqual(a, b) {
|
|
|
78
77
|
}
|
|
79
78
|
return true;
|
|
80
79
|
}
|
|
80
|
+
function derToPem(base64Der) {
|
|
81
|
+
const lines = base64Der.match(/.{1,64}/g) || [];
|
|
82
|
+
return `-----BEGIN CERTIFICATE-----
|
|
83
|
+
${lines.join("\n")}
|
|
84
|
+
-----END CERTIFICATE-----`;
|
|
85
|
+
}
|
|
86
|
+
function bytesToBase64url(bytes) {
|
|
87
|
+
let binary = "";
|
|
88
|
+
for (const byte of bytes) {
|
|
89
|
+
binary += String.fromCharCode(byte);
|
|
90
|
+
}
|
|
91
|
+
return globalThis.btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
92
|
+
}
|
|
81
93
|
function mapToCredentialClaims(raw) {
|
|
82
94
|
const claims = {};
|
|
83
95
|
if (typeof raw["age_over_18"] === "boolean") {
|
|
@@ -102,24 +114,27 @@ function mapToCredentialClaims(raw) {
|
|
|
102
114
|
}
|
|
103
115
|
return claims;
|
|
104
116
|
}
|
|
117
|
+
function extractCountryHint(issuer) {
|
|
118
|
+
try {
|
|
119
|
+
const url = new URL(issuer);
|
|
120
|
+
const tld = url.hostname.split(".").pop()?.toUpperCase();
|
|
121
|
+
if (tld && tld.length === 2) {
|
|
122
|
+
return tld;
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
return "";
|
|
127
|
+
}
|
|
105
128
|
var SdJwtParser = class {
|
|
106
129
|
format = "sd-jwt-vc";
|
|
107
|
-
/**
|
|
108
|
-
* Returns true when the token looks like an SD-JWT:
|
|
109
|
-
* it must be a string containing the `~` disclosure separator.
|
|
110
|
-
*/
|
|
111
130
|
canParse(vpToken) {
|
|
112
131
|
return typeof vpToken === "string" && vpToken.includes("~");
|
|
113
132
|
}
|
|
114
|
-
/**
|
|
115
|
-
* Parses and validates an SD-JWT VC token.
|
|
116
|
-
*
|
|
117
|
-
* @throws {MalformedCredentialError} when the token structure cannot be decoded
|
|
118
|
-
*/
|
|
119
133
|
async parse(vpToken, options) {
|
|
120
134
|
if (typeof vpToken !== "string") {
|
|
121
135
|
throw new MalformedCredentialError("VP token must be a string");
|
|
122
136
|
}
|
|
137
|
+
const allowedAlgorithms = options.allowedAlgorithms ?? DEFAULT_ALLOWED_ALGORITHMS;
|
|
123
138
|
const invalidResult = (error) => ({
|
|
124
139
|
valid: false,
|
|
125
140
|
format: this.format,
|
|
@@ -134,54 +149,95 @@ var SdJwtParser = class {
|
|
|
134
149
|
throw new MalformedCredentialError("Failed to split SD-JWT structure");
|
|
135
150
|
}
|
|
136
151
|
let header;
|
|
137
|
-
let payload;
|
|
138
152
|
try {
|
|
139
|
-
|
|
140
|
-
header = jwt.header;
|
|
141
|
-
payload = jwt.payload;
|
|
153
|
+
header = jose.decodeProtectedHeader(parts.jwt);
|
|
142
154
|
} catch {
|
|
143
|
-
throw new MalformedCredentialError("Failed to decode SD-JWT issuer JWT");
|
|
155
|
+
throw new MalformedCredentialError("Failed to decode SD-JWT issuer JWT header");
|
|
144
156
|
}
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
issuerInfo = {
|
|
153
|
-
certificate: issuerCertBytes,
|
|
154
|
-
country: extractCountryHint(issuerString)
|
|
155
|
-
};
|
|
156
|
-
} catch {
|
|
157
|
-
return invalidResult("Failed to extract issuer certificate from x5c");
|
|
158
|
-
}
|
|
157
|
+
const alg = header.alg;
|
|
158
|
+
if (typeof alg !== "string" || !allowedAlgorithms.includes(alg)) {
|
|
159
|
+
return invalidResult(`Unsupported algorithm: ${alg}`);
|
|
160
|
+
}
|
|
161
|
+
const x5cArray = header.x5c;
|
|
162
|
+
if (!Array.isArray(x5cArray) || x5cArray.length === 0 || typeof x5cArray[0] !== "string") {
|
|
163
|
+
throw new MalformedCredentialError("Missing or invalid x5c in JWT header");
|
|
159
164
|
}
|
|
165
|
+
let issuerKey;
|
|
166
|
+
let issuerCertBytes;
|
|
167
|
+
try {
|
|
168
|
+
issuerKey = await jose.importX509(derToPem(x5cArray[0]), alg);
|
|
169
|
+
issuerCertBytes = base64ToBytes(x5cArray[0]);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
if (err instanceof MalformedCredentialError) throw err;
|
|
172
|
+
throw new MalformedCredentialError("Failed to import issuer certificate");
|
|
173
|
+
}
|
|
174
|
+
let payload;
|
|
175
|
+
try {
|
|
176
|
+
const result = await jose.jwtVerify(parts.jwt, issuerKey, { algorithms: allowedAlgorithms });
|
|
177
|
+
payload = result.payload;
|
|
178
|
+
} catch {
|
|
179
|
+
return invalidResult("Issuer JWT signature verification failed");
|
|
180
|
+
}
|
|
181
|
+
const issuerString = typeof payload["iss"] === "string" ? payload["iss"] : "";
|
|
182
|
+
const issuerInfo = {
|
|
183
|
+
certificate: issuerCertBytes,
|
|
184
|
+
country: extractCountryHint(issuerString)
|
|
185
|
+
};
|
|
160
186
|
if (options.trustedCertificates.length > 0) {
|
|
161
187
|
const isTrusted = options.trustedCertificates.some((trusted) => bytesEqual(trusted, issuerCertBytes));
|
|
162
188
|
if (!isTrusted) {
|
|
163
189
|
return invalidResult("Issuer certificate is not trusted");
|
|
164
190
|
}
|
|
165
|
-
} else if (issuerCertBytes.length === 0) {
|
|
166
|
-
return invalidResult("No issuer certificate found and no trusted certificates configured");
|
|
167
191
|
}
|
|
168
|
-
const
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
192
|
+
const sdArray = payload["_sd"];
|
|
193
|
+
if (parts.disclosures.length > 0) {
|
|
194
|
+
if (!Array.isArray(sdArray) || sdArray.length === 0) {
|
|
195
|
+
return invalidResult("Disclosures present but no _sd array in issuer JWT");
|
|
196
|
+
}
|
|
197
|
+
for (const disclosure of parts.disclosures) {
|
|
198
|
+
const hashBytes = await sha256Hasher(disclosure);
|
|
199
|
+
const hashB64url = bytesToBase64url(hashBytes);
|
|
200
|
+
if (!sdArray.includes(hashB64url)) {
|
|
201
|
+
return invalidResult("Disclosure hash mismatch");
|
|
202
|
+
}
|
|
173
203
|
}
|
|
174
204
|
}
|
|
175
205
|
if (parts.kbJwt) {
|
|
206
|
+
const cnf = payload["cnf"];
|
|
207
|
+
if (!cnf || typeof cnf !== "object" || !cnf["jwk"]) {
|
|
208
|
+
throw new MalformedCredentialError("Missing cnf.jwk in issuer JWT for key binding");
|
|
209
|
+
}
|
|
210
|
+
let holderKey;
|
|
176
211
|
try {
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return invalidResult("Key binding JWT nonce does not match");
|
|
212
|
+
const kbHeader = jose.decodeProtectedHeader(parts.kbJwt);
|
|
213
|
+
const kbAlg = kbHeader.alg;
|
|
214
|
+
if (typeof kbAlg !== "string" || !allowedAlgorithms.includes(kbAlg)) {
|
|
215
|
+
return invalidResult(`Unsupported KB-JWT algorithm: ${kbAlg}`);
|
|
182
216
|
}
|
|
217
|
+
holderKey = await jose.importJWK(cnf["jwk"], kbAlg);
|
|
218
|
+
} catch (err) {
|
|
219
|
+
if (err instanceof MalformedCredentialError) throw err;
|
|
220
|
+
throw new MalformedCredentialError("Failed to import holder key from cnf.jwk");
|
|
221
|
+
}
|
|
222
|
+
let kbPayload;
|
|
223
|
+
try {
|
|
224
|
+
const kbResult = await jose.jwtVerify(parts.kbJwt, holderKey, {
|
|
225
|
+
algorithms: allowedAlgorithms,
|
|
226
|
+
...options.audience ? { audience: options.audience } : {}
|
|
227
|
+
});
|
|
228
|
+
kbPayload = kbResult.payload;
|
|
183
229
|
} catch {
|
|
184
|
-
return invalidResult("
|
|
230
|
+
return invalidResult("Key binding JWT signature verification failed");
|
|
231
|
+
}
|
|
232
|
+
if (kbPayload["nonce"] !== options.nonce) {
|
|
233
|
+
return invalidResult("Key binding JWT nonce does not match");
|
|
234
|
+
}
|
|
235
|
+
const disclosurePart = parts.disclosures.length > 0 ? parts.disclosures.join("~") + "~" : "";
|
|
236
|
+
const sdJwtForHash = parts.jwt + "~" + disclosurePart;
|
|
237
|
+
const sdHashBytes = await sha256Hasher(sdJwtForHash);
|
|
238
|
+
const expectedSdHash = bytesToBase64url(sdHashBytes);
|
|
239
|
+
if (kbPayload["sd_hash"] !== expectedSdHash) {
|
|
240
|
+
return invalidResult("Key binding JWT sd_hash does not match");
|
|
185
241
|
}
|
|
186
242
|
}
|
|
187
243
|
let resolvedClaims;
|
|
@@ -193,28 +249,16 @@ var SdJwtParser = class {
|
|
|
193
249
|
sha256Hasher
|
|
194
250
|
);
|
|
195
251
|
} catch {
|
|
196
|
-
|
|
252
|
+
throw new MalformedCredentialError("Failed to decode SD-JWT disclosures");
|
|
197
253
|
}
|
|
198
|
-
const claims = mapToCredentialClaims(resolvedClaims);
|
|
199
254
|
return {
|
|
200
255
|
valid: true,
|
|
201
256
|
format: this.format,
|
|
202
|
-
claims,
|
|
257
|
+
claims: mapToCredentialClaims(resolvedClaims),
|
|
203
258
|
issuer: issuerInfo
|
|
204
259
|
};
|
|
205
260
|
}
|
|
206
261
|
};
|
|
207
|
-
function extractCountryHint(issuer) {
|
|
208
|
-
try {
|
|
209
|
-
const url = new URL(issuer);
|
|
210
|
-
const tld = url.hostname.split(".").pop()?.toUpperCase();
|
|
211
|
-
if (tld && tld.length === 2) {
|
|
212
|
-
return tld;
|
|
213
|
-
}
|
|
214
|
-
} catch {
|
|
215
|
-
}
|
|
216
|
-
return "";
|
|
217
|
-
}
|
|
218
262
|
function isBinaryData(value) {
|
|
219
263
|
return value instanceof Uint8Array;
|
|
220
264
|
}
|
|
@@ -442,7 +486,7 @@ async function parsePresentation(vpToken, options) {
|
|
|
442
486
|
}
|
|
443
487
|
|
|
444
488
|
// src/index.ts
|
|
445
|
-
var VERSION = "0.
|
|
489
|
+
var VERSION = "0.2.0";
|
|
446
490
|
|
|
447
491
|
exports.ExpiredCredentialError = ExpiredCredentialError;
|
|
448
492
|
exports.InvalidSignatureError = InvalidSignatureError;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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":["splitSdJwt","decodeJwt","decodeSdJwt","getClaims","bytesEqual","decode","mapToCredentialClaims","uuidv4"],"mappings":"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","file":"index.cjs","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"]}
|
|
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":["splitSdJwt","decodeProtectedHeader","importX509","jwtVerify","importJWK","decodeSdJwt","getClaims","bytesEqual","decode","mapToCredentialClaims","uuidv4"],"mappings":"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","file":"index.cjs","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, decodeSdJwt, getClaims } from '@sd-jwt/decode';\nimport { decodeProtectedHeader, jwtVerify, importX509, importJWK } from 'jose';\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\nconst DEFAULT_ALLOWED_ALGORITHMS = ['ES256', 'ES384', 'ES512'];\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 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 * 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 * Converts a base64-encoded DER certificate to PEM format for jose.importX509.\n */\nfunction derToPem(base64Der: string): string {\n const lines = base64Der.match(/.{1,64}/g) || [];\n return `-----BEGIN CERTIFICATE-----\\n${lines.join('\\n')}\\n-----END CERTIFICATE-----`;\n}\n\n/**\n * Encodes a Uint8Array to base64url (no padding).\n * Used by disclosure hash verification (Task 5) and KB-JWT verification (Task 6).\n */\nfunction bytesToBase64url(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return globalThis.btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\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 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 * Best-effort extraction of a country code hint from an issuer URL or string.\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\n }\n return '';\n}\n\n/**\n * SD-JWT VC credential parser.\n *\n * Decodes and cryptographically validates SD-JWT Verifiable Credentials:\n * - JWT signature verification via x5c public key (jose)\n * - Issuer certificate trust chain verification\n * - Disclosure hash integrity verification\n * - Key binding JWT signature and sd_hash verification\n * - Selective disclosure claim extraction\n */\nexport class SdJwtParser implements ICredentialParser {\n readonly format: CredentialFormat = 'sd-jwt-vc';\n\n canParse(vpToken: unknown): boolean {\n return typeof vpToken === 'string' && vpToken.includes('~');\n }\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 allowedAlgorithms = options.allowedAlgorithms ?? DEFAULT_ALLOWED_ALGORITHMS;\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 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: Peek at issuer JWT header (need alg and x5c before verification)\n let header: ReturnType<typeof decodeProtectedHeader>;\n try {\n header = decodeProtectedHeader(parts.jwt);\n } catch {\n throw new MalformedCredentialError('Failed to decode SD-JWT issuer JWT header');\n }\n\n // Step 3: Check algorithm against allowlist\n const alg = header.alg;\n if (typeof alg !== 'string' || !allowedAlgorithms.includes(alg)) {\n return invalidResult(`Unsupported algorithm: ${alg}`);\n }\n\n // Step 4: Extract public key from x5c certificate\n const x5cArray = header.x5c;\n if (!Array.isArray(x5cArray) || x5cArray.length === 0 || typeof x5cArray[0] !== 'string') {\n throw new MalformedCredentialError('Missing or invalid x5c in JWT header');\n }\n\n let issuerKey: Awaited<ReturnType<typeof importX509>>;\n let issuerCertBytes: Uint8Array;\n try {\n issuerKey = await importX509(derToPem(x5cArray[0]), alg);\n issuerCertBytes = base64ToBytes(x5cArray[0]);\n } catch (err) {\n if (err instanceof MalformedCredentialError) throw err;\n throw new MalformedCredentialError('Failed to import issuer certificate');\n }\n\n // Step 5: Verify issuer JWT signature (also validates exp, iat via jose)\n let payload: Record<string, unknown>;\n try {\n const result = await jwtVerify(parts.jwt, issuerKey, { algorithms: allowedAlgorithms });\n payload = result.payload as unknown as Record<string, unknown>;\n } catch {\n return invalidResult('Issuer JWT signature verification failed');\n }\n\n // Step 6: Trust check — certificate must be in the trusted set\n const issuerString = typeof payload['iss'] === 'string' ? payload['iss'] : '';\n const issuerInfo: IssuerInfo = {\n certificate: issuerCertBytes,\n country: extractCountryHint(issuerString),\n };\n\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 }\n\n // Step 7: Verify disclosure hashes against _sd array\n const sdArray = payload['_sd'];\n if (parts.disclosures.length > 0) {\n if (!Array.isArray(sdArray) || sdArray.length === 0) {\n return invalidResult('Disclosures present but no _sd array in issuer JWT');\n }\n for (const disclosure of parts.disclosures) {\n const hashBytes = await sha256Hasher(disclosure, 'sha-256');\n const hashB64url = bytesToBase64url(hashBytes);\n if (!sdArray.includes(hashB64url)) {\n return invalidResult('Disclosure hash mismatch');\n }\n }\n }\n\n // Step 8: Verify key binding JWT (if present)\n if (parts.kbJwt) {\n // Extract holder public key from cnf claim\n const cnf = payload['cnf'] as Record<string, unknown> | undefined;\n if (!cnf || typeof cnf !== 'object' || !cnf['jwk']) {\n throw new MalformedCredentialError('Missing cnf.jwk in issuer JWT for key binding');\n }\n\n let holderKey: Awaited<ReturnType<typeof importJWK>>;\n try {\n const kbHeader = decodeProtectedHeader(parts.kbJwt);\n const kbAlg = kbHeader.alg;\n if (typeof kbAlg !== 'string' || !allowedAlgorithms.includes(kbAlg)) {\n return invalidResult(`Unsupported KB-JWT algorithm: ${kbAlg}`);\n }\n holderKey = await importJWK(cnf['jwk'] as Record<string, unknown>, kbAlg);\n } catch (err) {\n if (err instanceof MalformedCredentialError) throw err;\n throw new MalformedCredentialError('Failed to import holder key from cnf.jwk');\n }\n\n // Verify KB-JWT signature\n let kbPayload: Record<string, unknown>;\n try {\n const kbResult = await jwtVerify(parts.kbJwt, holderKey, {\n algorithms: allowedAlgorithms,\n ...(options.audience ? { audience: options.audience } : {}),\n });\n kbPayload = kbResult.payload as unknown as Record<string, unknown>;\n } catch {\n return invalidResult('Key binding JWT signature verification failed');\n }\n\n // Validate nonce — must be present and match when KB-JWT is used\n if (kbPayload['nonce'] !== options.nonce) {\n return invalidResult('Key binding JWT nonce does not match');\n }\n\n // Verify sd_hash — hash of the SD-JWT content before the KB-JWT\n const disclosurePart = parts.disclosures.length > 0 ? parts.disclosures.join('~') + '~' : '';\n const sdJwtForHash = parts.jwt + '~' + disclosurePart;\n const sdHashBytes = await sha256Hasher(sdJwtForHash, 'sha-256');\n const expectedSdHash = bytesToBase64url(sdHashBytes);\n if (kbPayload['sd_hash'] !== expectedSdHash) {\n return invalidResult('Key binding JWT sd_hash does not match');\n }\n }\n\n // Step 9: 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 throw new MalformedCredentialError('Failed to decode SD-JWT disclosures');\n }\n\n return {\n valid: true,\n format: this.format,\n claims: mapToCredentialClaims(resolvedClaims),\n issuer: issuerInfo,\n };\n }\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.2.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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -40,6 +40,10 @@ interface AuthorizationRequest {
|
|
|
40
40
|
interface ParseOptions {
|
|
41
41
|
trustedCertificates: Uint8Array[];
|
|
42
42
|
nonce: string;
|
|
43
|
+
/** Expected audience for key binding JWT verification. Optional. */
|
|
44
|
+
audience?: string;
|
|
45
|
+
/** Allowed JWT signature algorithms. Defaults to ['ES256', 'ES384', 'ES512']. */
|
|
46
|
+
allowedAlgorithms?: string[];
|
|
43
47
|
}
|
|
44
48
|
interface ICredentialParser {
|
|
45
49
|
readonly format: CredentialFormat;
|
|
@@ -50,26 +54,16 @@ interface ICredentialParser {
|
|
|
50
54
|
/**
|
|
51
55
|
* SD-JWT VC credential parser.
|
|
52
56
|
*
|
|
53
|
-
* Decodes and validates SD-JWT Verifiable Credentials
|
|
54
|
-
*
|
|
55
|
-
* - Structure validation (JWT~disclosure~kb format)
|
|
57
|
+
* Decodes and cryptographically validates SD-JWT Verifiable Credentials:
|
|
58
|
+
* - JWT signature verification via x5c public key (jose)
|
|
56
59
|
* - Issuer certificate trust chain verification
|
|
57
|
-
* -
|
|
58
|
-
* -
|
|
60
|
+
* - Disclosure hash integrity verification
|
|
61
|
+
* - Key binding JWT signature and sd_hash verification
|
|
59
62
|
* - Selective disclosure claim extraction
|
|
60
63
|
*/
|
|
61
64
|
declare class SdJwtParser implements ICredentialParser {
|
|
62
65
|
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
66
|
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
67
|
parse(vpToken: unknown, options: ParseOptions): Promise<PresentationResult>;
|
|
74
68
|
}
|
|
75
69
|
|
|
@@ -120,6 +114,6 @@ declare function createAuthorizationRequest(input: AuthorizationRequestInput): A
|
|
|
120
114
|
|
|
121
115
|
declare function parsePresentation(vpToken: unknown, options: ParseOptions): Promise<PresentationResult>;
|
|
122
116
|
|
|
123
|
-
declare const VERSION = "0.
|
|
117
|
+
declare const VERSION = "0.2.0";
|
|
124
118
|
|
|
125
119
|
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.d.ts
CHANGED
|
@@ -40,6 +40,10 @@ interface AuthorizationRequest {
|
|
|
40
40
|
interface ParseOptions {
|
|
41
41
|
trustedCertificates: Uint8Array[];
|
|
42
42
|
nonce: string;
|
|
43
|
+
/** Expected audience for key binding JWT verification. Optional. */
|
|
44
|
+
audience?: string;
|
|
45
|
+
/** Allowed JWT signature algorithms. Defaults to ['ES256', 'ES384', 'ES512']. */
|
|
46
|
+
allowedAlgorithms?: string[];
|
|
43
47
|
}
|
|
44
48
|
interface ICredentialParser {
|
|
45
49
|
readonly format: CredentialFormat;
|
|
@@ -50,26 +54,16 @@ interface ICredentialParser {
|
|
|
50
54
|
/**
|
|
51
55
|
* SD-JWT VC credential parser.
|
|
52
56
|
*
|
|
53
|
-
* Decodes and validates SD-JWT Verifiable Credentials
|
|
54
|
-
*
|
|
55
|
-
* - Structure validation (JWT~disclosure~kb format)
|
|
57
|
+
* Decodes and cryptographically validates SD-JWT Verifiable Credentials:
|
|
58
|
+
* - JWT signature verification via x5c public key (jose)
|
|
56
59
|
* - Issuer certificate trust chain verification
|
|
57
|
-
* -
|
|
58
|
-
* -
|
|
60
|
+
* - Disclosure hash integrity verification
|
|
61
|
+
* - Key binding JWT signature and sd_hash verification
|
|
59
62
|
* - Selective disclosure claim extraction
|
|
60
63
|
*/
|
|
61
64
|
declare class SdJwtParser implements ICredentialParser {
|
|
62
65
|
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
66
|
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
67
|
parse(vpToken: unknown, options: ParseOptions): Promise<PresentationResult>;
|
|
74
68
|
}
|
|
75
69
|
|
|
@@ -120,6 +114,6 @@ declare function createAuthorizationRequest(input: AuthorizationRequestInput): A
|
|
|
120
114
|
|
|
121
115
|
declare function parsePresentation(vpToken: unknown, options: ParseOptions): Promise<PresentationResult>;
|
|
122
116
|
|
|
123
|
-
declare const VERSION = "0.
|
|
117
|
+
declare const VERSION = "0.2.0";
|
|
124
118
|
|
|
125
119
|
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
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { splitSdJwt,
|
|
1
|
+
import { splitSdJwt, decodeSdJwt, getClaims } from '@sd-jwt/decode';
|
|
2
|
+
import { decodeProtectedHeader, importX509, jwtVerify, importJWK } from 'jose';
|
|
2
3
|
import { decode } from 'cbor-x';
|
|
3
4
|
import { v4 } from 'uuid';
|
|
4
5
|
|
|
@@ -42,6 +43,7 @@ var NonceValidationError = class extends Error {
|
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
// src/parsers/sd-jwt.parser.ts
|
|
46
|
+
var DEFAULT_ALLOWED_ALGORITHMS = ["ES256", "ES384", "ES512"];
|
|
45
47
|
var sha256Hasher = async (data, _alg) => {
|
|
46
48
|
const encoder = new TextEncoder();
|
|
47
49
|
const input = typeof data === "string" ? encoder.encode(data) : data;
|
|
@@ -66,9 +68,6 @@ function base64ToBytes(base64) {
|
|
|
66
68
|
}
|
|
67
69
|
return bytes;
|
|
68
70
|
}
|
|
69
|
-
function extractCertificateFromX5c(x5cEntry) {
|
|
70
|
-
return base64ToBytes(x5cEntry);
|
|
71
|
-
}
|
|
72
71
|
function bytesEqual(a, b) {
|
|
73
72
|
if (a.length !== b.length) return false;
|
|
74
73
|
for (let i = 0; i < a.length; i++) {
|
|
@@ -76,6 +75,19 @@ function bytesEqual(a, b) {
|
|
|
76
75
|
}
|
|
77
76
|
return true;
|
|
78
77
|
}
|
|
78
|
+
function derToPem(base64Der) {
|
|
79
|
+
const lines = base64Der.match(/.{1,64}/g) || [];
|
|
80
|
+
return `-----BEGIN CERTIFICATE-----
|
|
81
|
+
${lines.join("\n")}
|
|
82
|
+
-----END CERTIFICATE-----`;
|
|
83
|
+
}
|
|
84
|
+
function bytesToBase64url(bytes) {
|
|
85
|
+
let binary = "";
|
|
86
|
+
for (const byte of bytes) {
|
|
87
|
+
binary += String.fromCharCode(byte);
|
|
88
|
+
}
|
|
89
|
+
return globalThis.btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
90
|
+
}
|
|
79
91
|
function mapToCredentialClaims(raw) {
|
|
80
92
|
const claims = {};
|
|
81
93
|
if (typeof raw["age_over_18"] === "boolean") {
|
|
@@ -100,24 +112,27 @@ function mapToCredentialClaims(raw) {
|
|
|
100
112
|
}
|
|
101
113
|
return claims;
|
|
102
114
|
}
|
|
115
|
+
function extractCountryHint(issuer) {
|
|
116
|
+
try {
|
|
117
|
+
const url = new URL(issuer);
|
|
118
|
+
const tld = url.hostname.split(".").pop()?.toUpperCase();
|
|
119
|
+
if (tld && tld.length === 2) {
|
|
120
|
+
return tld;
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
103
126
|
var SdJwtParser = class {
|
|
104
127
|
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
128
|
canParse(vpToken) {
|
|
110
129
|
return typeof vpToken === "string" && vpToken.includes("~");
|
|
111
130
|
}
|
|
112
|
-
/**
|
|
113
|
-
* Parses and validates an SD-JWT VC token.
|
|
114
|
-
*
|
|
115
|
-
* @throws {MalformedCredentialError} when the token structure cannot be decoded
|
|
116
|
-
*/
|
|
117
131
|
async parse(vpToken, options) {
|
|
118
132
|
if (typeof vpToken !== "string") {
|
|
119
133
|
throw new MalformedCredentialError("VP token must be a string");
|
|
120
134
|
}
|
|
135
|
+
const allowedAlgorithms = options.allowedAlgorithms ?? DEFAULT_ALLOWED_ALGORITHMS;
|
|
121
136
|
const invalidResult = (error) => ({
|
|
122
137
|
valid: false,
|
|
123
138
|
format: this.format,
|
|
@@ -132,54 +147,95 @@ var SdJwtParser = class {
|
|
|
132
147
|
throw new MalformedCredentialError("Failed to split SD-JWT structure");
|
|
133
148
|
}
|
|
134
149
|
let header;
|
|
135
|
-
let payload;
|
|
136
150
|
try {
|
|
137
|
-
|
|
138
|
-
header = jwt.header;
|
|
139
|
-
payload = jwt.payload;
|
|
151
|
+
header = decodeProtectedHeader(parts.jwt);
|
|
140
152
|
} catch {
|
|
141
|
-
throw new MalformedCredentialError("Failed to decode SD-JWT issuer JWT");
|
|
153
|
+
throw new MalformedCredentialError("Failed to decode SD-JWT issuer JWT header");
|
|
142
154
|
}
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
issuerInfo = {
|
|
151
|
-
certificate: issuerCertBytes,
|
|
152
|
-
country: extractCountryHint(issuerString)
|
|
153
|
-
};
|
|
154
|
-
} catch {
|
|
155
|
-
return invalidResult("Failed to extract issuer certificate from x5c");
|
|
156
|
-
}
|
|
155
|
+
const alg = header.alg;
|
|
156
|
+
if (typeof alg !== "string" || !allowedAlgorithms.includes(alg)) {
|
|
157
|
+
return invalidResult(`Unsupported algorithm: ${alg}`);
|
|
158
|
+
}
|
|
159
|
+
const x5cArray = header.x5c;
|
|
160
|
+
if (!Array.isArray(x5cArray) || x5cArray.length === 0 || typeof x5cArray[0] !== "string") {
|
|
161
|
+
throw new MalformedCredentialError("Missing or invalid x5c in JWT header");
|
|
157
162
|
}
|
|
163
|
+
let issuerKey;
|
|
164
|
+
let issuerCertBytes;
|
|
165
|
+
try {
|
|
166
|
+
issuerKey = await importX509(derToPem(x5cArray[0]), alg);
|
|
167
|
+
issuerCertBytes = base64ToBytes(x5cArray[0]);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
if (err instanceof MalformedCredentialError) throw err;
|
|
170
|
+
throw new MalformedCredentialError("Failed to import issuer certificate");
|
|
171
|
+
}
|
|
172
|
+
let payload;
|
|
173
|
+
try {
|
|
174
|
+
const result = await jwtVerify(parts.jwt, issuerKey, { algorithms: allowedAlgorithms });
|
|
175
|
+
payload = result.payload;
|
|
176
|
+
} catch {
|
|
177
|
+
return invalidResult("Issuer JWT signature verification failed");
|
|
178
|
+
}
|
|
179
|
+
const issuerString = typeof payload["iss"] === "string" ? payload["iss"] : "";
|
|
180
|
+
const issuerInfo = {
|
|
181
|
+
certificate: issuerCertBytes,
|
|
182
|
+
country: extractCountryHint(issuerString)
|
|
183
|
+
};
|
|
158
184
|
if (options.trustedCertificates.length > 0) {
|
|
159
185
|
const isTrusted = options.trustedCertificates.some((trusted) => bytesEqual(trusted, issuerCertBytes));
|
|
160
186
|
if (!isTrusted) {
|
|
161
187
|
return invalidResult("Issuer certificate is not trusted");
|
|
162
188
|
}
|
|
163
|
-
} else if (issuerCertBytes.length === 0) {
|
|
164
|
-
return invalidResult("No issuer certificate found and no trusted certificates configured");
|
|
165
189
|
}
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
190
|
+
const sdArray = payload["_sd"];
|
|
191
|
+
if (parts.disclosures.length > 0) {
|
|
192
|
+
if (!Array.isArray(sdArray) || sdArray.length === 0) {
|
|
193
|
+
return invalidResult("Disclosures present but no _sd array in issuer JWT");
|
|
194
|
+
}
|
|
195
|
+
for (const disclosure of parts.disclosures) {
|
|
196
|
+
const hashBytes = await sha256Hasher(disclosure);
|
|
197
|
+
const hashB64url = bytesToBase64url(hashBytes);
|
|
198
|
+
if (!sdArray.includes(hashB64url)) {
|
|
199
|
+
return invalidResult("Disclosure hash mismatch");
|
|
200
|
+
}
|
|
171
201
|
}
|
|
172
202
|
}
|
|
173
203
|
if (parts.kbJwt) {
|
|
204
|
+
const cnf = payload["cnf"];
|
|
205
|
+
if (!cnf || typeof cnf !== "object" || !cnf["jwk"]) {
|
|
206
|
+
throw new MalformedCredentialError("Missing cnf.jwk in issuer JWT for key binding");
|
|
207
|
+
}
|
|
208
|
+
let holderKey;
|
|
174
209
|
try {
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return invalidResult("Key binding JWT nonce does not match");
|
|
210
|
+
const kbHeader = decodeProtectedHeader(parts.kbJwt);
|
|
211
|
+
const kbAlg = kbHeader.alg;
|
|
212
|
+
if (typeof kbAlg !== "string" || !allowedAlgorithms.includes(kbAlg)) {
|
|
213
|
+
return invalidResult(`Unsupported KB-JWT algorithm: ${kbAlg}`);
|
|
180
214
|
}
|
|
215
|
+
holderKey = await importJWK(cnf["jwk"], kbAlg);
|
|
216
|
+
} catch (err) {
|
|
217
|
+
if (err instanceof MalformedCredentialError) throw err;
|
|
218
|
+
throw new MalformedCredentialError("Failed to import holder key from cnf.jwk");
|
|
219
|
+
}
|
|
220
|
+
let kbPayload;
|
|
221
|
+
try {
|
|
222
|
+
const kbResult = await jwtVerify(parts.kbJwt, holderKey, {
|
|
223
|
+
algorithms: allowedAlgorithms,
|
|
224
|
+
...options.audience ? { audience: options.audience } : {}
|
|
225
|
+
});
|
|
226
|
+
kbPayload = kbResult.payload;
|
|
181
227
|
} catch {
|
|
182
|
-
return invalidResult("
|
|
228
|
+
return invalidResult("Key binding JWT signature verification failed");
|
|
229
|
+
}
|
|
230
|
+
if (kbPayload["nonce"] !== options.nonce) {
|
|
231
|
+
return invalidResult("Key binding JWT nonce does not match");
|
|
232
|
+
}
|
|
233
|
+
const disclosurePart = parts.disclosures.length > 0 ? parts.disclosures.join("~") + "~" : "";
|
|
234
|
+
const sdJwtForHash = parts.jwt + "~" + disclosurePart;
|
|
235
|
+
const sdHashBytes = await sha256Hasher(sdJwtForHash);
|
|
236
|
+
const expectedSdHash = bytesToBase64url(sdHashBytes);
|
|
237
|
+
if (kbPayload["sd_hash"] !== expectedSdHash) {
|
|
238
|
+
return invalidResult("Key binding JWT sd_hash does not match");
|
|
183
239
|
}
|
|
184
240
|
}
|
|
185
241
|
let resolvedClaims;
|
|
@@ -191,28 +247,16 @@ var SdJwtParser = class {
|
|
|
191
247
|
sha256Hasher
|
|
192
248
|
);
|
|
193
249
|
} catch {
|
|
194
|
-
|
|
250
|
+
throw new MalformedCredentialError("Failed to decode SD-JWT disclosures");
|
|
195
251
|
}
|
|
196
|
-
const claims = mapToCredentialClaims(resolvedClaims);
|
|
197
252
|
return {
|
|
198
253
|
valid: true,
|
|
199
254
|
format: this.format,
|
|
200
|
-
claims,
|
|
255
|
+
claims: mapToCredentialClaims(resolvedClaims),
|
|
201
256
|
issuer: issuerInfo
|
|
202
257
|
};
|
|
203
258
|
}
|
|
204
259
|
};
|
|
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
260
|
function isBinaryData(value) {
|
|
217
261
|
return value instanceof Uint8Array;
|
|
218
262
|
}
|
|
@@ -440,7 +484,7 @@ async function parsePresentation(vpToken, options) {
|
|
|
440
484
|
}
|
|
441
485
|
|
|
442
486
|
// src/index.ts
|
|
443
|
-
var VERSION = "0.
|
|
487
|
+
var VERSION = "0.2.0";
|
|
444
488
|
|
|
445
489
|
export { ExpiredCredentialError, InvalidSignatureError, MalformedCredentialError, MdocParser, NonceValidationError, SdJwtParser, UnsupportedFormatError, VERSION, createAuthorizationRequest, parsePresentation };
|
|
446
490
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"]}
|
|
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, decodeSdJwt, getClaims } from '@sd-jwt/decode';\nimport { decodeProtectedHeader, jwtVerify, importX509, importJWK } from 'jose';\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\nconst DEFAULT_ALLOWED_ALGORITHMS = ['ES256', 'ES384', 'ES512'];\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 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 * 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 * Converts a base64-encoded DER certificate to PEM format for jose.importX509.\n */\nfunction derToPem(base64Der: string): string {\n const lines = base64Der.match(/.{1,64}/g) || [];\n return `-----BEGIN CERTIFICATE-----\\n${lines.join('\\n')}\\n-----END CERTIFICATE-----`;\n}\n\n/**\n * Encodes a Uint8Array to base64url (no padding).\n * Used by disclosure hash verification (Task 5) and KB-JWT verification (Task 6).\n */\nfunction bytesToBase64url(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return globalThis.btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\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 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 * Best-effort extraction of a country code hint from an issuer URL or string.\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\n }\n return '';\n}\n\n/**\n * SD-JWT VC credential parser.\n *\n * Decodes and cryptographically validates SD-JWT Verifiable Credentials:\n * - JWT signature verification via x5c public key (jose)\n * - Issuer certificate trust chain verification\n * - Disclosure hash integrity verification\n * - Key binding JWT signature and sd_hash verification\n * - Selective disclosure claim extraction\n */\nexport class SdJwtParser implements ICredentialParser {\n readonly format: CredentialFormat = 'sd-jwt-vc';\n\n canParse(vpToken: unknown): boolean {\n return typeof vpToken === 'string' && vpToken.includes('~');\n }\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 allowedAlgorithms = options.allowedAlgorithms ?? DEFAULT_ALLOWED_ALGORITHMS;\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 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: Peek at issuer JWT header (need alg and x5c before verification)\n let header: ReturnType<typeof decodeProtectedHeader>;\n try {\n header = decodeProtectedHeader(parts.jwt);\n } catch {\n throw new MalformedCredentialError('Failed to decode SD-JWT issuer JWT header');\n }\n\n // Step 3: Check algorithm against allowlist\n const alg = header.alg;\n if (typeof alg !== 'string' || !allowedAlgorithms.includes(alg)) {\n return invalidResult(`Unsupported algorithm: ${alg}`);\n }\n\n // Step 4: Extract public key from x5c certificate\n const x5cArray = header.x5c;\n if (!Array.isArray(x5cArray) || x5cArray.length === 0 || typeof x5cArray[0] !== 'string') {\n throw new MalformedCredentialError('Missing or invalid x5c in JWT header');\n }\n\n let issuerKey: Awaited<ReturnType<typeof importX509>>;\n let issuerCertBytes: Uint8Array;\n try {\n issuerKey = await importX509(derToPem(x5cArray[0]), alg);\n issuerCertBytes = base64ToBytes(x5cArray[0]);\n } catch (err) {\n if (err instanceof MalformedCredentialError) throw err;\n throw new MalformedCredentialError('Failed to import issuer certificate');\n }\n\n // Step 5: Verify issuer JWT signature (also validates exp, iat via jose)\n let payload: Record<string, unknown>;\n try {\n const result = await jwtVerify(parts.jwt, issuerKey, { algorithms: allowedAlgorithms });\n payload = result.payload as unknown as Record<string, unknown>;\n } catch {\n return invalidResult('Issuer JWT signature verification failed');\n }\n\n // Step 6: Trust check — certificate must be in the trusted set\n const issuerString = typeof payload['iss'] === 'string' ? payload['iss'] : '';\n const issuerInfo: IssuerInfo = {\n certificate: issuerCertBytes,\n country: extractCountryHint(issuerString),\n };\n\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 }\n\n // Step 7: Verify disclosure hashes against _sd array\n const sdArray = payload['_sd'];\n if (parts.disclosures.length > 0) {\n if (!Array.isArray(sdArray) || sdArray.length === 0) {\n return invalidResult('Disclosures present but no _sd array in issuer JWT');\n }\n for (const disclosure of parts.disclosures) {\n const hashBytes = await sha256Hasher(disclosure, 'sha-256');\n const hashB64url = bytesToBase64url(hashBytes);\n if (!sdArray.includes(hashB64url)) {\n return invalidResult('Disclosure hash mismatch');\n }\n }\n }\n\n // Step 8: Verify key binding JWT (if present)\n if (parts.kbJwt) {\n // Extract holder public key from cnf claim\n const cnf = payload['cnf'] as Record<string, unknown> | undefined;\n if (!cnf || typeof cnf !== 'object' || !cnf['jwk']) {\n throw new MalformedCredentialError('Missing cnf.jwk in issuer JWT for key binding');\n }\n\n let holderKey: Awaited<ReturnType<typeof importJWK>>;\n try {\n const kbHeader = decodeProtectedHeader(parts.kbJwt);\n const kbAlg = kbHeader.alg;\n if (typeof kbAlg !== 'string' || !allowedAlgorithms.includes(kbAlg)) {\n return invalidResult(`Unsupported KB-JWT algorithm: ${kbAlg}`);\n }\n holderKey = await importJWK(cnf['jwk'] as Record<string, unknown>, kbAlg);\n } catch (err) {\n if (err instanceof MalformedCredentialError) throw err;\n throw new MalformedCredentialError('Failed to import holder key from cnf.jwk');\n }\n\n // Verify KB-JWT signature\n let kbPayload: Record<string, unknown>;\n try {\n const kbResult = await jwtVerify(parts.kbJwt, holderKey, {\n algorithms: allowedAlgorithms,\n ...(options.audience ? { audience: options.audience } : {}),\n });\n kbPayload = kbResult.payload as unknown as Record<string, unknown>;\n } catch {\n return invalidResult('Key binding JWT signature verification failed');\n }\n\n // Validate nonce — must be present and match when KB-JWT is used\n if (kbPayload['nonce'] !== options.nonce) {\n return invalidResult('Key binding JWT nonce does not match');\n }\n\n // Verify sd_hash — hash of the SD-JWT content before the KB-JWT\n const disclosurePart = parts.disclosures.length > 0 ? parts.disclosures.join('~') + '~' : '';\n const sdJwtForHash = parts.jwt + '~' + disclosurePart;\n const sdHashBytes = await sha256Hasher(sdJwtForHash, 'sha-256');\n const expectedSdHash = bytesToBase64url(sdHashBytes);\n if (kbPayload['sd_hash'] !== expectedSdHash) {\n return invalidResult('Key binding JWT sd_hash does not match');\n }\n }\n\n // Step 9: 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 throw new MalformedCredentialError('Failed to decode SD-JWT disclosures');\n }\n\n return {\n valid: true,\n format: this.format,\n claims: mapToCredentialClaims(resolvedClaims),\n issuer: issuerInfo,\n };\n }\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.2.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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openeudi/openid4vp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "OpenID4VP credential parsing and validation for EUDI Wallets (SD-JWT VC + mDOC)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -32,16 +32,19 @@
|
|
|
32
32
|
"type-check": "tsc --noEmit"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
+
"@sd-jwt/decode": "^0.19.0",
|
|
35
36
|
"@sphereon/pex": "5.0.0-unstable.28",
|
|
36
37
|
"@sphereon/ssi-types": "^0.37.1",
|
|
37
|
-
"@sd-jwt/decode": "^0.19.0",
|
|
38
38
|
"cbor-x": "^1.6.0",
|
|
39
39
|
"cose-js": "^0.9.0",
|
|
40
|
+
"jose": "^6.1.3",
|
|
40
41
|
"uuid": "^11.1.0"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
|
-
"
|
|
44
|
+
"@peculiar/x509": "^2.0.0",
|
|
45
|
+
"reflect-metadata": "^0.2.2",
|
|
44
46
|
"tsup": "^8.5.1",
|
|
47
|
+
"typescript": "^5.8.3",
|
|
45
48
|
"vitest": "^4.0.18"
|
|
46
49
|
},
|
|
47
50
|
"keywords": [
|