@openeudi/openid4vp 0.7.0 → 0.8.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/README.md +4 -0
- package/dist/index.cjs +133 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +133 -27
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -338,6 +338,10 @@ This library implements the **verifier side** of OpenID4VP for SD-JWT VC and mDO
|
|
|
338
338
|
|
|
339
339
|
EUDI Architecture Reference Framework (ARF) alignment: tracks OpenID4VP 1.0 final. Full ARF 1.4+ profile compliance will be added before a stable 1.0.
|
|
340
340
|
|
|
341
|
+
## OIDF Conformance Testing
|
|
342
|
+
|
|
343
|
+
Verifier-side conformance is automated against a self-hosted OpenID Foundation conformance suite. See `docs/manual-testing/oidf-interop.md` for both the CI orchestrator (`npm run oidf:ci -- --profile=happy-flow|full`) and the manual hosted-demo escape hatch.
|
|
344
|
+
|
|
341
345
|
## Related packages
|
|
342
346
|
|
|
343
347
|
- **[@openeudi/core](https://www.npmjs.com/package/@openeudi/core)** -- Framework-agnostic EUDI Wallet verification protocol engine with session management and QR code generation.
|
package/dist/index.cjs
CHANGED
|
@@ -8159,7 +8159,10 @@ async function createSignedAuthorizationRequest(input, query) {
|
|
|
8159
8159
|
}
|
|
8160
8160
|
const jwk = { ...input.encryptionKey.publicJwk, use: "enc" };
|
|
8161
8161
|
clientMetadata.jwks = { keys: [jwk] };
|
|
8162
|
-
|
|
8162
|
+
const encValues = enc ?? [...DEFAULT_SUPPORTED_ENC_VALUES];
|
|
8163
|
+
clientMetadata.encrypted_response_enc_values_supported = encValues;
|
|
8164
|
+
clientMetadata.authorization_encrypted_response_alg = "ECDH-ES";
|
|
8165
|
+
clientMetadata.authorization_encrypted_response_enc = encValues[0];
|
|
8163
8166
|
}
|
|
8164
8167
|
const payload = {
|
|
8165
8168
|
iss: clientId,
|
|
@@ -8418,7 +8421,7 @@ async function verifyCoseSign1(coseSign1, publicKey, allowedAlgorithms) {
|
|
|
8418
8421
|
throw new exports.MalformedCredentialError(`no hash mapping for alg ${coseSign1.alg}`);
|
|
8419
8422
|
}
|
|
8420
8423
|
const sigStructure = ["Signature1", coseSign1.protectedHeaderBytes, new Uint8Array(0), coseSign1.payload];
|
|
8421
|
-
const sigInput = decoder.encode(sigStructure);
|
|
8424
|
+
const sigInput = new Uint8Array(decoder.encode(sigStructure));
|
|
8422
8425
|
const ok = await crypto.subtle.verify(
|
|
8423
8426
|
{ name: "ECDSA", hash: hashAlg },
|
|
8424
8427
|
publicKey,
|
|
@@ -8957,32 +8960,74 @@ var SdJwtParser = class {
|
|
|
8957
8960
|
return invalidResult(`Unsupported algorithm: ${alg}`);
|
|
8958
8961
|
}
|
|
8959
8962
|
const x5cArray = header.x5c;
|
|
8960
|
-
|
|
8961
|
-
throw new exports.MalformedCredentialError("Missing or invalid x5c in JWT header");
|
|
8962
|
-
}
|
|
8963
|
+
const hasX5c = Array.isArray(x5cArray) && x5cArray.length > 0 && typeof x5cArray[0] === "string";
|
|
8963
8964
|
let issuerKey;
|
|
8964
8965
|
let issuerCertBytes;
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
|
|
8966
|
+
if (hasX5c) {
|
|
8967
|
+
try {
|
|
8968
|
+
issuerKey = await jose.importX509(derToPem2(x5cArray[0]), alg);
|
|
8969
|
+
issuerCertBytes = base64ToBytes(x5cArray[0]);
|
|
8970
|
+
} catch (err) {
|
|
8971
|
+
if (err instanceof exports.MalformedCredentialError) throw err;
|
|
8972
|
+
throw new exports.MalformedCredentialError("Failed to import issuer certificate");
|
|
8973
|
+
}
|
|
8974
|
+
} else if (options.trustedIssuerJwks && options.trustedIssuerJwks.length > 0) {
|
|
8975
|
+
issuerKey = void 0;
|
|
8976
|
+
} else {
|
|
8977
|
+
throw new exports.MalformedCredentialError("Missing or invalid x5c in JWT header");
|
|
8971
8978
|
}
|
|
8972
8979
|
let payload;
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8980
|
+
if (issuerKey) {
|
|
8981
|
+
try {
|
|
8982
|
+
const result2 = await jose.jwtVerify(parts.jwt, issuerKey, { algorithms: allowedAlgorithms });
|
|
8983
|
+
payload = result2.payload;
|
|
8984
|
+
} catch {
|
|
8985
|
+
return invalidResult("Issuer JWT signature verification failed");
|
|
8986
|
+
}
|
|
8987
|
+
} else {
|
|
8988
|
+
const jwks = options.trustedIssuerJwks;
|
|
8989
|
+
const headerKid = header["kid"];
|
|
8990
|
+
let candidates;
|
|
8991
|
+
if (typeof headerKid === "string") {
|
|
8992
|
+
const matched = jwks.find((k) => k["kid"] === headerKid);
|
|
8993
|
+
if (!matched) {
|
|
8994
|
+
return invalidResult(
|
|
8995
|
+
`No matching JWK found in trustedIssuerJwks for kid=${headerKid}`
|
|
8996
|
+
);
|
|
8997
|
+
}
|
|
8998
|
+
candidates = [matched];
|
|
8999
|
+
} else {
|
|
9000
|
+
candidates = jwks;
|
|
9001
|
+
}
|
|
9002
|
+
let verifiedPayload;
|
|
9003
|
+
for (const jwk of candidates) {
|
|
9004
|
+
let candidateKey;
|
|
9005
|
+
try {
|
|
9006
|
+
candidateKey = await jose.importJWK(jwk, alg);
|
|
9007
|
+
} catch {
|
|
9008
|
+
continue;
|
|
9009
|
+
}
|
|
9010
|
+
try {
|
|
9011
|
+
const result2 = await jose.jwtVerify(parts.jwt, candidateKey, {
|
|
9012
|
+
algorithms: allowedAlgorithms
|
|
9013
|
+
});
|
|
9014
|
+
verifiedPayload = result2.payload;
|
|
9015
|
+
break;
|
|
9016
|
+
} catch {
|
|
9017
|
+
}
|
|
9018
|
+
}
|
|
9019
|
+
if (!verifiedPayload) {
|
|
9020
|
+
return invalidResult("Issuer JWT signature verification failed");
|
|
9021
|
+
}
|
|
9022
|
+
payload = verifiedPayload;
|
|
8978
9023
|
}
|
|
8979
9024
|
const issuerString = typeof payload["iss"] === "string" ? payload["iss"] : "";
|
|
8980
9025
|
const issuerInfo = {
|
|
8981
|
-
certificate: issuerCertBytes,
|
|
9026
|
+
certificate: issuerCertBytes ?? new Uint8Array(),
|
|
8982
9027
|
country: extractCountryHint(issuerString)
|
|
8983
9028
|
};
|
|
8984
9029
|
let trustResult;
|
|
8985
|
-
if (options.trustStore) {
|
|
9030
|
+
if (issuerCertBytes === void 0) ; else if (options.trustStore) {
|
|
8986
9031
|
const { TrustEvaluator: TrustEvaluator2 } = await Promise.resolve().then(() => (init_TrustEvaluator(), TrustEvaluator_exports));
|
|
8987
9032
|
const evaluator = new TrustEvaluator2({
|
|
8988
9033
|
trustStore: options.trustStore,
|
|
@@ -9002,24 +9047,56 @@ var SdJwtParser = class {
|
|
|
9002
9047
|
);
|
|
9003
9048
|
}
|
|
9004
9049
|
} else {
|
|
9005
|
-
const isTrusted = options.trustedCertificates.some(
|
|
9050
|
+
const isTrusted = options.trustedCertificates.some(
|
|
9051
|
+
(trusted) => bytesEqual4(trusted, issuerCertBytes)
|
|
9052
|
+
);
|
|
9006
9053
|
if (!isTrusted) {
|
|
9007
9054
|
return invalidResult("Issuer certificate is not trusted");
|
|
9008
9055
|
}
|
|
9009
9056
|
}
|
|
9010
9057
|
}
|
|
9011
|
-
const sdArray = payload["_sd"];
|
|
9012
9058
|
if (parts.disclosures.length > 0) {
|
|
9013
|
-
|
|
9014
|
-
return invalidResult("Disclosures present but no _sd array in issuer JWT");
|
|
9015
|
-
}
|
|
9059
|
+
const decodedDisclosures = [];
|
|
9016
9060
|
for (const disclosure of parts.disclosures) {
|
|
9061
|
+
let parsed;
|
|
9062
|
+
try {
|
|
9063
|
+
parsed = JSON.parse(new TextDecoder().decode(base64ToBytes(disclosure)));
|
|
9064
|
+
} catch {
|
|
9065
|
+
return invalidResult("Disclosure is not valid base64url-encoded JSON");
|
|
9066
|
+
}
|
|
9067
|
+
if (!Array.isArray(parsed) || parsed.length !== 2 && parsed.length !== 3) {
|
|
9068
|
+
return invalidResult("Disclosure must be a 2- or 3-element array");
|
|
9069
|
+
}
|
|
9017
9070
|
const hashBytes = await sha256Hasher(disclosure);
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9071
|
+
decodedDisclosures.push({
|
|
9072
|
+
raw: disclosure,
|
|
9073
|
+
hash: bytesToBase64url(hashBytes),
|
|
9074
|
+
length: parsed.length,
|
|
9075
|
+
value: parsed.length === 3 ? parsed[2] : parsed[1]
|
|
9076
|
+
});
|
|
9077
|
+
}
|
|
9078
|
+
const seed = collectAnchorHashes(payload);
|
|
9079
|
+
const kvHashes = seed.kvHashes;
|
|
9080
|
+
const arrayElemHashes = seed.arrayElemHashes;
|
|
9081
|
+
const remaining = [...decodedDisclosures];
|
|
9082
|
+
for (; ; ) {
|
|
9083
|
+
const anchored = [];
|
|
9084
|
+
for (const d of remaining) {
|
|
9085
|
+
const valid = d.length === 3 ? kvHashes.has(d.hash) : arrayElemHashes.has(d.hash);
|
|
9086
|
+
if (valid) anchored.push(d);
|
|
9087
|
+
}
|
|
9088
|
+
if (anchored.length === 0) break;
|
|
9089
|
+
for (const a of anchored) {
|
|
9090
|
+
const sub = collectAnchorHashes(a.value);
|
|
9091
|
+
for (const h of sub.kvHashes) kvHashes.add(h);
|
|
9092
|
+
for (const h of sub.arrayElemHashes) arrayElemHashes.add(h);
|
|
9093
|
+
const idx = remaining.indexOf(a);
|
|
9094
|
+
if (idx >= 0) remaining.splice(idx, 1);
|
|
9021
9095
|
}
|
|
9022
9096
|
}
|
|
9097
|
+
if (remaining.length > 0) {
|
|
9098
|
+
return invalidResult("Disclosure hash mismatch");
|
|
9099
|
+
}
|
|
9023
9100
|
}
|
|
9024
9101
|
if (parts.kbJwt) {
|
|
9025
9102
|
const cnf = payload["cnf"];
|
|
@@ -9082,6 +9159,35 @@ var SdJwtParser = class {
|
|
|
9082
9159
|
return result;
|
|
9083
9160
|
}
|
|
9084
9161
|
};
|
|
9162
|
+
function collectAnchorHashes(node) {
|
|
9163
|
+
const kvHashes = /* @__PURE__ */ new Set();
|
|
9164
|
+
const arrayElemHashes = /* @__PURE__ */ new Set();
|
|
9165
|
+
const visit = (value) => {
|
|
9166
|
+
if (Array.isArray(value)) {
|
|
9167
|
+
for (const item of value) {
|
|
9168
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
9169
|
+
const obj = item;
|
|
9170
|
+
const keys = Object.keys(obj);
|
|
9171
|
+
if (keys.length === 1 && keys[0] === "..." && typeof obj["..."] === "string") {
|
|
9172
|
+
arrayElemHashes.add(obj["..."]);
|
|
9173
|
+
continue;
|
|
9174
|
+
}
|
|
9175
|
+
}
|
|
9176
|
+
visit(item);
|
|
9177
|
+
}
|
|
9178
|
+
} else if (value && typeof value === "object") {
|
|
9179
|
+
for (const [k, v] of Object.entries(value)) {
|
|
9180
|
+
if (k === "_sd" && Array.isArray(v)) {
|
|
9181
|
+
for (const h of v) if (typeof h === "string") kvHashes.add(h);
|
|
9182
|
+
continue;
|
|
9183
|
+
}
|
|
9184
|
+
visit(v);
|
|
9185
|
+
}
|
|
9186
|
+
}
|
|
9187
|
+
};
|
|
9188
|
+
visit(node);
|
|
9189
|
+
return { kvHashes, arrayElemHashes };
|
|
9190
|
+
}
|
|
9085
9191
|
|
|
9086
9192
|
// src/presentation.ts
|
|
9087
9193
|
var parsers = [new SdJwtParser(), new MdocParser()];
|
|
@@ -9821,7 +9927,7 @@ function equalBytes(a, b) {
|
|
|
9821
9927
|
|
|
9822
9928
|
// src/index.ts
|
|
9823
9929
|
init_errors();
|
|
9824
|
-
var VERSION = "0.
|
|
9930
|
+
var VERSION = "0.8.0";
|
|
9825
9931
|
/*! Bundled license information:
|
|
9826
9932
|
|
|
9827
9933
|
pvtsutils/build/index.es.js:
|