@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/dist/index.d.cts
CHANGED
|
@@ -317,6 +317,27 @@ interface ParseOptions {
|
|
|
317
317
|
cache?: Cache;
|
|
318
318
|
/** Clock-skew tolerance in seconds for certificate validity checks. Default 60. */
|
|
319
319
|
clockSkewTolerance?: number;
|
|
320
|
+
/**
|
|
321
|
+
* An explicit set of trusted issuer JWKs used as an alternate trust path when the
|
|
322
|
+
* SD-JWT VC's issuer JWT lacks an `x5c` header. When the header has no `x5c`:
|
|
323
|
+
* - If this array is provided, the parser looks up the issuer key by `kid`
|
|
324
|
+
* (or by exact `kty`/`crv`/`x`/`y` match when no `kid` is present). The
|
|
325
|
+
* matched JWK is the trust anchor — cert-chain validation is skipped.
|
|
326
|
+
* - If this array is absent (or empty), the parser falls back to the existing
|
|
327
|
+
* behaviour and throws `MalformedCredentialError('Missing or invalid x5c in JWT header')`.
|
|
328
|
+
*
|
|
329
|
+
* **When the JWT header DOES include `x5c`** this option is ignored — the existing
|
|
330
|
+
* x5c/`trustedCertificates` / `trustStore` path runs unchanged.
|
|
331
|
+
*
|
|
332
|
+
* **Security notice**: opting in to this path skips certificate-chain trust
|
|
333
|
+
* evaluation entirely. The caller is explicitly asserting that they trust the
|
|
334
|
+
* supplied JWK(s) out-of-band. This is appropriate for CI harness setups (e.g.
|
|
335
|
+
* OIDF conformance suite, where the wallet signs without x5c and the verifier
|
|
336
|
+
* knows the signing key from the test-plan config). It is **not recommended for
|
|
337
|
+
* production verifiers** — cert-chain trust evaluation via `trustStore` is the
|
|
338
|
+
* secure path.
|
|
339
|
+
*/
|
|
340
|
+
trustedIssuerJwks?: JsonWebKey[];
|
|
320
341
|
}
|
|
321
342
|
interface ICredentialParser {
|
|
322
343
|
readonly format: CredentialFormat;
|
|
@@ -555,6 +576,6 @@ declare class LotlTrustStore implements TrustStore {
|
|
|
555
576
|
private refresh;
|
|
556
577
|
}
|
|
557
578
|
|
|
558
|
-
declare const VERSION = "0.
|
|
579
|
+
declare const VERSION = "0.8.0";
|
|
559
580
|
|
|
560
581
|
export { type AuthorizationRequest, type AuthorizationRequestInput, type AuthorizationResponse, type Cache, CertificateChainError, type ChainErrorReason, CompositeTrustStore, type CredentialClaims, type CredentialFormat, DecryptionFailedError, type EncryptedResponse, ExpiredCredentialError, type Fetcher, HAIP_DOCTYPE_NAMESPACES, type HaipQueryInput, type HaipValidationCode, HaipValidationError, type ICredentialParser, InMemoryCache, InvalidSignatureError, type IssuerInfo, type LotlAnchorMetadata, LotlFetchError, LotlSignatureError, LotlTrustStore, type LotlTrustStoreOptions, MalformedCredentialError, MdocParser, MissingDecryptionKeyError, MultipleCredentialsNotSupportedError, NonceValidationError, OpenID4VPError, type ParseOptions, type PresentationResult, RevocationCheckFailedError, RevokedCertificateError, SdJwtParser, type SignedAuthorizationRequest, type SignedAuthorizationRequestInput, SignedRequestBuildError, type SignedRequestBuildErrorCode, StaticTrustStore, type TrustAnchor, TrustAnchorNotFoundError, type TrustStore, type TrustStoreHint, type TrustStoreInput, UnsupportedFormatError, UnsupportedJweError, VERSION, type VerifyAuthorizationResponseOptions, type VerifyOptions, type VerifyResult, buildHaipQuery, createAuthorizationRequest, createSignedAuthorizationRequest, decryptAuthorizationResponse, isHaipQuery, parsePresentation, validateHaipQuery, verifyAuthorizationResponse, verifyPresentation };
|
package/dist/index.d.ts
CHANGED
|
@@ -317,6 +317,27 @@ interface ParseOptions {
|
|
|
317
317
|
cache?: Cache;
|
|
318
318
|
/** Clock-skew tolerance in seconds for certificate validity checks. Default 60. */
|
|
319
319
|
clockSkewTolerance?: number;
|
|
320
|
+
/**
|
|
321
|
+
* An explicit set of trusted issuer JWKs used as an alternate trust path when the
|
|
322
|
+
* SD-JWT VC's issuer JWT lacks an `x5c` header. When the header has no `x5c`:
|
|
323
|
+
* - If this array is provided, the parser looks up the issuer key by `kid`
|
|
324
|
+
* (or by exact `kty`/`crv`/`x`/`y` match when no `kid` is present). The
|
|
325
|
+
* matched JWK is the trust anchor — cert-chain validation is skipped.
|
|
326
|
+
* - If this array is absent (or empty), the parser falls back to the existing
|
|
327
|
+
* behaviour and throws `MalformedCredentialError('Missing or invalid x5c in JWT header')`.
|
|
328
|
+
*
|
|
329
|
+
* **When the JWT header DOES include `x5c`** this option is ignored — the existing
|
|
330
|
+
* x5c/`trustedCertificates` / `trustStore` path runs unchanged.
|
|
331
|
+
*
|
|
332
|
+
* **Security notice**: opting in to this path skips certificate-chain trust
|
|
333
|
+
* evaluation entirely. The caller is explicitly asserting that they trust the
|
|
334
|
+
* supplied JWK(s) out-of-band. This is appropriate for CI harness setups (e.g.
|
|
335
|
+
* OIDF conformance suite, where the wallet signs without x5c and the verifier
|
|
336
|
+
* knows the signing key from the test-plan config). It is **not recommended for
|
|
337
|
+
* production verifiers** — cert-chain trust evaluation via `trustStore` is the
|
|
338
|
+
* secure path.
|
|
339
|
+
*/
|
|
340
|
+
trustedIssuerJwks?: JsonWebKey[];
|
|
320
341
|
}
|
|
321
342
|
interface ICredentialParser {
|
|
322
343
|
readonly format: CredentialFormat;
|
|
@@ -555,6 +576,6 @@ declare class LotlTrustStore implements TrustStore {
|
|
|
555
576
|
private refresh;
|
|
556
577
|
}
|
|
557
578
|
|
|
558
|
-
declare const VERSION = "0.
|
|
579
|
+
declare const VERSION = "0.8.0";
|
|
559
580
|
|
|
560
581
|
export { type AuthorizationRequest, type AuthorizationRequestInput, type AuthorizationResponse, type Cache, CertificateChainError, type ChainErrorReason, CompositeTrustStore, type CredentialClaims, type CredentialFormat, DecryptionFailedError, type EncryptedResponse, ExpiredCredentialError, type Fetcher, HAIP_DOCTYPE_NAMESPACES, type HaipQueryInput, type HaipValidationCode, HaipValidationError, type ICredentialParser, InMemoryCache, InvalidSignatureError, type IssuerInfo, type LotlAnchorMetadata, LotlFetchError, LotlSignatureError, LotlTrustStore, type LotlTrustStoreOptions, MalformedCredentialError, MdocParser, MissingDecryptionKeyError, MultipleCredentialsNotSupportedError, NonceValidationError, OpenID4VPError, type ParseOptions, type PresentationResult, RevocationCheckFailedError, RevokedCertificateError, SdJwtParser, type SignedAuthorizationRequest, type SignedAuthorizationRequestInput, SignedRequestBuildError, type SignedRequestBuildErrorCode, StaticTrustStore, type TrustAnchor, TrustAnchorNotFoundError, type TrustStore, type TrustStoreHint, type TrustStoreInput, UnsupportedFormatError, UnsupportedJweError, VERSION, type VerifyAuthorizationResponseOptions, type VerifyOptions, type VerifyResult, buildHaipQuery, createAuthorizationRequest, createSignedAuthorizationRequest, decryptAuthorizationResponse, isHaipQuery, parsePresentation, validateHaipQuery, verifyAuthorizationResponse, verifyPresentation };
|
package/dist/index.js
CHANGED
|
@@ -8138,7 +8138,10 @@ async function createSignedAuthorizationRequest(input, query) {
|
|
|
8138
8138
|
}
|
|
8139
8139
|
const jwk = { ...input.encryptionKey.publicJwk, use: "enc" };
|
|
8140
8140
|
clientMetadata.jwks = { keys: [jwk] };
|
|
8141
|
-
|
|
8141
|
+
const encValues = enc ?? [...DEFAULT_SUPPORTED_ENC_VALUES];
|
|
8142
|
+
clientMetadata.encrypted_response_enc_values_supported = encValues;
|
|
8143
|
+
clientMetadata.authorization_encrypted_response_alg = "ECDH-ES";
|
|
8144
|
+
clientMetadata.authorization_encrypted_response_enc = encValues[0];
|
|
8142
8145
|
}
|
|
8143
8146
|
const payload = {
|
|
8144
8147
|
iss: clientId,
|
|
@@ -8397,7 +8400,7 @@ async function verifyCoseSign1(coseSign1, publicKey, allowedAlgorithms) {
|
|
|
8397
8400
|
throw new MalformedCredentialError(`no hash mapping for alg ${coseSign1.alg}`);
|
|
8398
8401
|
}
|
|
8399
8402
|
const sigStructure = ["Signature1", coseSign1.protectedHeaderBytes, new Uint8Array(0), coseSign1.payload];
|
|
8400
|
-
const sigInput = decoder.encode(sigStructure);
|
|
8403
|
+
const sigInput = new Uint8Array(decoder.encode(sigStructure));
|
|
8401
8404
|
const ok = await crypto.subtle.verify(
|
|
8402
8405
|
{ name: "ECDSA", hash: hashAlg },
|
|
8403
8406
|
publicKey,
|
|
@@ -8936,32 +8939,74 @@ var SdJwtParser = class {
|
|
|
8936
8939
|
return invalidResult(`Unsupported algorithm: ${alg}`);
|
|
8937
8940
|
}
|
|
8938
8941
|
const x5cArray = header.x5c;
|
|
8939
|
-
|
|
8940
|
-
throw new MalformedCredentialError("Missing or invalid x5c in JWT header");
|
|
8941
|
-
}
|
|
8942
|
+
const hasX5c = Array.isArray(x5cArray) && x5cArray.length > 0 && typeof x5cArray[0] === "string";
|
|
8942
8943
|
let issuerKey;
|
|
8943
8944
|
let issuerCertBytes;
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8945
|
+
if (hasX5c) {
|
|
8946
|
+
try {
|
|
8947
|
+
issuerKey = await importX509(derToPem2(x5cArray[0]), alg);
|
|
8948
|
+
issuerCertBytes = base64ToBytes(x5cArray[0]);
|
|
8949
|
+
} catch (err) {
|
|
8950
|
+
if (err instanceof MalformedCredentialError) throw err;
|
|
8951
|
+
throw new MalformedCredentialError("Failed to import issuer certificate");
|
|
8952
|
+
}
|
|
8953
|
+
} else if (options.trustedIssuerJwks && options.trustedIssuerJwks.length > 0) {
|
|
8954
|
+
issuerKey = void 0;
|
|
8955
|
+
} else {
|
|
8956
|
+
throw new MalformedCredentialError("Missing or invalid x5c in JWT header");
|
|
8950
8957
|
}
|
|
8951
8958
|
let payload;
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8959
|
+
if (issuerKey) {
|
|
8960
|
+
try {
|
|
8961
|
+
const result2 = await jwtVerify(parts.jwt, issuerKey, { algorithms: allowedAlgorithms });
|
|
8962
|
+
payload = result2.payload;
|
|
8963
|
+
} catch {
|
|
8964
|
+
return invalidResult("Issuer JWT signature verification failed");
|
|
8965
|
+
}
|
|
8966
|
+
} else {
|
|
8967
|
+
const jwks = options.trustedIssuerJwks;
|
|
8968
|
+
const headerKid = header["kid"];
|
|
8969
|
+
let candidates;
|
|
8970
|
+
if (typeof headerKid === "string") {
|
|
8971
|
+
const matched = jwks.find((k) => k["kid"] === headerKid);
|
|
8972
|
+
if (!matched) {
|
|
8973
|
+
return invalidResult(
|
|
8974
|
+
`No matching JWK found in trustedIssuerJwks for kid=${headerKid}`
|
|
8975
|
+
);
|
|
8976
|
+
}
|
|
8977
|
+
candidates = [matched];
|
|
8978
|
+
} else {
|
|
8979
|
+
candidates = jwks;
|
|
8980
|
+
}
|
|
8981
|
+
let verifiedPayload;
|
|
8982
|
+
for (const jwk of candidates) {
|
|
8983
|
+
let candidateKey;
|
|
8984
|
+
try {
|
|
8985
|
+
candidateKey = await importJWK(jwk, alg);
|
|
8986
|
+
} catch {
|
|
8987
|
+
continue;
|
|
8988
|
+
}
|
|
8989
|
+
try {
|
|
8990
|
+
const result2 = await jwtVerify(parts.jwt, candidateKey, {
|
|
8991
|
+
algorithms: allowedAlgorithms
|
|
8992
|
+
});
|
|
8993
|
+
verifiedPayload = result2.payload;
|
|
8994
|
+
break;
|
|
8995
|
+
} catch {
|
|
8996
|
+
}
|
|
8997
|
+
}
|
|
8998
|
+
if (!verifiedPayload) {
|
|
8999
|
+
return invalidResult("Issuer JWT signature verification failed");
|
|
9000
|
+
}
|
|
9001
|
+
payload = verifiedPayload;
|
|
8957
9002
|
}
|
|
8958
9003
|
const issuerString = typeof payload["iss"] === "string" ? payload["iss"] : "";
|
|
8959
9004
|
const issuerInfo = {
|
|
8960
|
-
certificate: issuerCertBytes,
|
|
9005
|
+
certificate: issuerCertBytes ?? new Uint8Array(),
|
|
8961
9006
|
country: extractCountryHint(issuerString)
|
|
8962
9007
|
};
|
|
8963
9008
|
let trustResult;
|
|
8964
|
-
if (options.trustStore) {
|
|
9009
|
+
if (issuerCertBytes === void 0) ; else if (options.trustStore) {
|
|
8965
9010
|
const { TrustEvaluator: TrustEvaluator2 } = await Promise.resolve().then(() => (init_TrustEvaluator(), TrustEvaluator_exports));
|
|
8966
9011
|
const evaluator = new TrustEvaluator2({
|
|
8967
9012
|
trustStore: options.trustStore,
|
|
@@ -8981,24 +9026,56 @@ var SdJwtParser = class {
|
|
|
8981
9026
|
);
|
|
8982
9027
|
}
|
|
8983
9028
|
} else {
|
|
8984
|
-
const isTrusted = options.trustedCertificates.some(
|
|
9029
|
+
const isTrusted = options.trustedCertificates.some(
|
|
9030
|
+
(trusted) => bytesEqual4(trusted, issuerCertBytes)
|
|
9031
|
+
);
|
|
8985
9032
|
if (!isTrusted) {
|
|
8986
9033
|
return invalidResult("Issuer certificate is not trusted");
|
|
8987
9034
|
}
|
|
8988
9035
|
}
|
|
8989
9036
|
}
|
|
8990
|
-
const sdArray = payload["_sd"];
|
|
8991
9037
|
if (parts.disclosures.length > 0) {
|
|
8992
|
-
|
|
8993
|
-
return invalidResult("Disclosures present but no _sd array in issuer JWT");
|
|
8994
|
-
}
|
|
9038
|
+
const decodedDisclosures = [];
|
|
8995
9039
|
for (const disclosure of parts.disclosures) {
|
|
9040
|
+
let parsed;
|
|
9041
|
+
try {
|
|
9042
|
+
parsed = JSON.parse(new TextDecoder().decode(base64ToBytes(disclosure)));
|
|
9043
|
+
} catch {
|
|
9044
|
+
return invalidResult("Disclosure is not valid base64url-encoded JSON");
|
|
9045
|
+
}
|
|
9046
|
+
if (!Array.isArray(parsed) || parsed.length !== 2 && parsed.length !== 3) {
|
|
9047
|
+
return invalidResult("Disclosure must be a 2- or 3-element array");
|
|
9048
|
+
}
|
|
8996
9049
|
const hashBytes = await sha256Hasher(disclosure);
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9050
|
+
decodedDisclosures.push({
|
|
9051
|
+
raw: disclosure,
|
|
9052
|
+
hash: bytesToBase64url(hashBytes),
|
|
9053
|
+
length: parsed.length,
|
|
9054
|
+
value: parsed.length === 3 ? parsed[2] : parsed[1]
|
|
9055
|
+
});
|
|
9056
|
+
}
|
|
9057
|
+
const seed = collectAnchorHashes(payload);
|
|
9058
|
+
const kvHashes = seed.kvHashes;
|
|
9059
|
+
const arrayElemHashes = seed.arrayElemHashes;
|
|
9060
|
+
const remaining = [...decodedDisclosures];
|
|
9061
|
+
for (; ; ) {
|
|
9062
|
+
const anchored = [];
|
|
9063
|
+
for (const d of remaining) {
|
|
9064
|
+
const valid = d.length === 3 ? kvHashes.has(d.hash) : arrayElemHashes.has(d.hash);
|
|
9065
|
+
if (valid) anchored.push(d);
|
|
9066
|
+
}
|
|
9067
|
+
if (anchored.length === 0) break;
|
|
9068
|
+
for (const a of anchored) {
|
|
9069
|
+
const sub = collectAnchorHashes(a.value);
|
|
9070
|
+
for (const h of sub.kvHashes) kvHashes.add(h);
|
|
9071
|
+
for (const h of sub.arrayElemHashes) arrayElemHashes.add(h);
|
|
9072
|
+
const idx = remaining.indexOf(a);
|
|
9073
|
+
if (idx >= 0) remaining.splice(idx, 1);
|
|
9000
9074
|
}
|
|
9001
9075
|
}
|
|
9076
|
+
if (remaining.length > 0) {
|
|
9077
|
+
return invalidResult("Disclosure hash mismatch");
|
|
9078
|
+
}
|
|
9002
9079
|
}
|
|
9003
9080
|
if (parts.kbJwt) {
|
|
9004
9081
|
const cnf = payload["cnf"];
|
|
@@ -9061,6 +9138,35 @@ var SdJwtParser = class {
|
|
|
9061
9138
|
return result;
|
|
9062
9139
|
}
|
|
9063
9140
|
};
|
|
9141
|
+
function collectAnchorHashes(node) {
|
|
9142
|
+
const kvHashes = /* @__PURE__ */ new Set();
|
|
9143
|
+
const arrayElemHashes = /* @__PURE__ */ new Set();
|
|
9144
|
+
const visit = (value) => {
|
|
9145
|
+
if (Array.isArray(value)) {
|
|
9146
|
+
for (const item of value) {
|
|
9147
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
9148
|
+
const obj = item;
|
|
9149
|
+
const keys = Object.keys(obj);
|
|
9150
|
+
if (keys.length === 1 && keys[0] === "..." && typeof obj["..."] === "string") {
|
|
9151
|
+
arrayElemHashes.add(obj["..."]);
|
|
9152
|
+
continue;
|
|
9153
|
+
}
|
|
9154
|
+
}
|
|
9155
|
+
visit(item);
|
|
9156
|
+
}
|
|
9157
|
+
} else if (value && typeof value === "object") {
|
|
9158
|
+
for (const [k, v] of Object.entries(value)) {
|
|
9159
|
+
if (k === "_sd" && Array.isArray(v)) {
|
|
9160
|
+
for (const h of v) if (typeof h === "string") kvHashes.add(h);
|
|
9161
|
+
continue;
|
|
9162
|
+
}
|
|
9163
|
+
visit(v);
|
|
9164
|
+
}
|
|
9165
|
+
}
|
|
9166
|
+
};
|
|
9167
|
+
visit(node);
|
|
9168
|
+
return { kvHashes, arrayElemHashes };
|
|
9169
|
+
}
|
|
9064
9170
|
|
|
9065
9171
|
// src/presentation.ts
|
|
9066
9172
|
var parsers = [new SdJwtParser(), new MdocParser()];
|
|
@@ -9800,7 +9906,7 @@ function equalBytes(a, b) {
|
|
|
9800
9906
|
|
|
9801
9907
|
// src/index.ts
|
|
9802
9908
|
init_errors();
|
|
9803
|
-
var VERSION = "0.
|
|
9909
|
+
var VERSION = "0.8.0";
|
|
9804
9910
|
/*! Bundled license information:
|
|
9805
9911
|
|
|
9806
9912
|
pvtsutils/build/index.es.js:
|