@openeudi/openid4vp 0.7.0 → 0.8.1
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 +197 -50
- 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 +197 -50
- 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
|
@@ -6361,6 +6361,33 @@ var init_es20152 = __esm({
|
|
|
6361
6361
|
init_validity();
|
|
6362
6362
|
}
|
|
6363
6363
|
});
|
|
6364
|
+
function getSkiHex(cert) {
|
|
6365
|
+
const ext = cert.getExtension(SubjectKeyIdentifierExtension);
|
|
6366
|
+
return ext ? ext.keyId.toLowerCase() : null;
|
|
6367
|
+
}
|
|
6368
|
+
function certificatesEqual(a, b) {
|
|
6369
|
+
const ab = new Uint8Array(a.rawData);
|
|
6370
|
+
const bb = new Uint8Array(b.rawData);
|
|
6371
|
+
if (ab.length !== bb.length) return false;
|
|
6372
|
+
for (let i = 0; i < ab.length; i++) {
|
|
6373
|
+
if (ab[i] !== bb[i]) return false;
|
|
6374
|
+
}
|
|
6375
|
+
return true;
|
|
6376
|
+
}
|
|
6377
|
+
function getSkiBytes(cert) {
|
|
6378
|
+
const ext = cert.getExtension(SubjectKeyIdentifierExtension);
|
|
6379
|
+
if (!ext) return null;
|
|
6380
|
+
const hex = ext.keyId;
|
|
6381
|
+
const out = new Uint8Array(hex.length / 2);
|
|
6382
|
+
for (let i = 0; i < out.length; i++) {
|
|
6383
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
6384
|
+
}
|
|
6385
|
+
return out;
|
|
6386
|
+
}
|
|
6387
|
+
var init_x509_utils = __esm({
|
|
6388
|
+
"src/trust/x509-utils.ts"() {
|
|
6389
|
+
}
|
|
6390
|
+
});
|
|
6364
6391
|
function mapX509AlgoToJwaName(cert) {
|
|
6365
6392
|
const alg = cert.signatureAlgorithm;
|
|
6366
6393
|
const name = alg?.name ?? "";
|
|
@@ -6370,8 +6397,11 @@ function mapX509AlgoToJwaName(cert) {
|
|
|
6370
6397
|
if (name === "ECDSA" && hash === "SHA-512") return "ES512";
|
|
6371
6398
|
if (name === "Ed25519" || name === "EdDSA") return "EdDSA";
|
|
6372
6399
|
if (name === "RSASSA-PKCS1-v1_5" && hash === "SHA-256") return "RS256";
|
|
6400
|
+
if (name === "RSASSA-PKCS1-v1_5" && hash === "SHA-384") return "RS384";
|
|
6401
|
+
if (name === "RSASSA-PKCS1-v1_5" && hash === "SHA-512") return "RS512";
|
|
6373
6402
|
if (name === "RSA-PSS" && hash === "SHA-256") return "PS256";
|
|
6374
6403
|
if (name === "RSA-PSS" && hash === "SHA-384") return "PS384";
|
|
6404
|
+
if (name === "RSA-PSS" && hash === "SHA-512") return "PS512";
|
|
6375
6405
|
return `${name}-${hash}`;
|
|
6376
6406
|
}
|
|
6377
6407
|
function collectLeafNames(leaf) {
|
|
@@ -6464,8 +6494,20 @@ var init_ChainBuilder = __esm({
|
|
|
6464
6494
|
init_es2015();
|
|
6465
6495
|
init_es20152();
|
|
6466
6496
|
init_errors();
|
|
6497
|
+
init_x509_utils();
|
|
6467
6498
|
NAME_CONSTRAINTS_OID = "2.5.29.30";
|
|
6468
|
-
DEFAULT_ALGORITHMS = [
|
|
6499
|
+
DEFAULT_ALGORITHMS = [
|
|
6500
|
+
"ES256",
|
|
6501
|
+
"ES384",
|
|
6502
|
+
"ES512",
|
|
6503
|
+
"EdDSA",
|
|
6504
|
+
"RS256",
|
|
6505
|
+
"RS384",
|
|
6506
|
+
"RS512",
|
|
6507
|
+
"PS256",
|
|
6508
|
+
"PS384",
|
|
6509
|
+
"PS512"
|
|
6510
|
+
];
|
|
6469
6511
|
ChainBuilder = class {
|
|
6470
6512
|
constructor(opts = {}) {
|
|
6471
6513
|
this.opts = opts;
|
|
@@ -6524,6 +6566,12 @@ var init_ChainBuilder = __esm({
|
|
|
6524
6566
|
current = issuer;
|
|
6525
6567
|
nonLeafDepth += 1;
|
|
6526
6568
|
}
|
|
6569
|
+
if (!certificatesEqual(current, anchor)) {
|
|
6570
|
+
throw new CertificateChainError(
|
|
6571
|
+
`certificate ${current.subject} shares the trust anchor's Subject DN but is not the anchor (no signature closure)`,
|
|
6572
|
+
{ reason: "signature" }
|
|
6573
|
+
);
|
|
6574
|
+
}
|
|
6527
6575
|
this.checkNameConstraints(leaf, chain.slice(1));
|
|
6528
6576
|
return chain;
|
|
6529
6577
|
}
|
|
@@ -7764,7 +7812,10 @@ var init_RevocationChecker = __esm({
|
|
|
7764
7812
|
if (hints.ocspUrl) {
|
|
7765
7813
|
try {
|
|
7766
7814
|
return await this.ocsp.checkCached(subjectCert, issuerCert, hints.ocspUrl, now);
|
|
7767
|
-
} catch {
|
|
7815
|
+
} catch (err) {
|
|
7816
|
+
console.warn(
|
|
7817
|
+
`[openid4vp] OCSP check failed for ${subjectCert.subject} via ${hints.ocspUrl}, falling back to CRL: ${err.message}`
|
|
7818
|
+
);
|
|
7768
7819
|
}
|
|
7769
7820
|
}
|
|
7770
7821
|
for (const url of hints.crlUrls) {
|
|
@@ -7792,6 +7843,9 @@ var init_RevocationChecker = __esm({
|
|
|
7792
7843
|
return { status: "good", source: "crl", checkedAt: now };
|
|
7793
7844
|
} catch (err) {
|
|
7794
7845
|
if (err instanceof RevocationCheckFailedError) throw err;
|
|
7846
|
+
console.warn(
|
|
7847
|
+
`[openid4vp] CRL check failed for ${subjectCert.subject} via ${url}: ${err.message}`
|
|
7848
|
+
);
|
|
7795
7849
|
continue;
|
|
7796
7850
|
}
|
|
7797
7851
|
}
|
|
@@ -7810,24 +7864,6 @@ var init_RevocationChecker = __esm({
|
|
|
7810
7864
|
};
|
|
7811
7865
|
}
|
|
7812
7866
|
});
|
|
7813
|
-
function getSkiHex(cert) {
|
|
7814
|
-
const ext = cert.getExtension(SubjectKeyIdentifierExtension);
|
|
7815
|
-
return ext ? ext.keyId.toLowerCase() : null;
|
|
7816
|
-
}
|
|
7817
|
-
function getSkiBytes(cert) {
|
|
7818
|
-
const ext = cert.getExtension(SubjectKeyIdentifierExtension);
|
|
7819
|
-
if (!ext) return null;
|
|
7820
|
-
const hex = ext.keyId;
|
|
7821
|
-
const out = new Uint8Array(hex.length / 2);
|
|
7822
|
-
for (let i = 0; i < out.length; i++) {
|
|
7823
|
-
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
7824
|
-
}
|
|
7825
|
-
return out;
|
|
7826
|
-
}
|
|
7827
|
-
var init_x509_utils = __esm({
|
|
7828
|
-
"src/trust/x509-utils.ts"() {
|
|
7829
|
-
}
|
|
7830
|
-
});
|
|
7831
7867
|
|
|
7832
7868
|
// src/trust/lotl-loa-mapping.ts
|
|
7833
7869
|
function mapLoA(uris) {
|
|
@@ -8009,8 +8045,13 @@ var init_TrustEvaluator = __esm({
|
|
|
8009
8045
|
);
|
|
8010
8046
|
const terminusCert = chain[chain.length - 1];
|
|
8011
8047
|
const anchor = anchors.find(
|
|
8012
|
-
(a) => a.certificate
|
|
8013
|
-
)
|
|
8048
|
+
(a) => certificatesEqual(a.certificate, terminusCert)
|
|
8049
|
+
);
|
|
8050
|
+
if (!anchor) {
|
|
8051
|
+
throw new TrustAnchorNotFoundError(
|
|
8052
|
+
`chain terminus ${terminusCert.subject} does not match any trust anchor certificate`
|
|
8053
|
+
);
|
|
8054
|
+
}
|
|
8014
8055
|
const issuerForRevocation = chain.length > 1 ? chain[1] : terminusCert;
|
|
8015
8056
|
const revocation = await this.revocationChecker.check(
|
|
8016
8057
|
leaf,
|
|
@@ -8138,7 +8179,10 @@ async function createSignedAuthorizationRequest(input, query) {
|
|
|
8138
8179
|
}
|
|
8139
8180
|
const jwk = { ...input.encryptionKey.publicJwk, use: "enc" };
|
|
8140
8181
|
clientMetadata.jwks = { keys: [jwk] };
|
|
8141
|
-
|
|
8182
|
+
const encValues = enc ?? [...DEFAULT_SUPPORTED_ENC_VALUES];
|
|
8183
|
+
clientMetadata.encrypted_response_enc_values_supported = encValues;
|
|
8184
|
+
clientMetadata.authorization_encrypted_response_alg = "ECDH-ES";
|
|
8185
|
+
clientMetadata.authorization_encrypted_response_enc = encValues[0];
|
|
8142
8186
|
}
|
|
8143
8187
|
const payload = {
|
|
8144
8188
|
iss: clientId,
|
|
@@ -8311,7 +8355,7 @@ function isHaipQuery(query) {
|
|
|
8311
8355
|
|
|
8312
8356
|
// src/crypto/cose-sign1.ts
|
|
8313
8357
|
init_errors();
|
|
8314
|
-
var decoder = new Encoder({ mapsAsObjects: false, useRecords: false });
|
|
8358
|
+
var decoder = new Encoder({ mapsAsObjects: false, useRecords: false, tagUint8Array: false });
|
|
8315
8359
|
var HEADER_ALG = 1;
|
|
8316
8360
|
var HEADER_X5CHAIN = 33;
|
|
8317
8361
|
var COSE_ALG_TO_NAME = {
|
|
@@ -8397,7 +8441,7 @@ async function verifyCoseSign1(coseSign1, publicKey, allowedAlgorithms) {
|
|
|
8397
8441
|
throw new MalformedCredentialError(`no hash mapping for alg ${coseSign1.alg}`);
|
|
8398
8442
|
}
|
|
8399
8443
|
const sigStructure = ["Signature1", coseSign1.protectedHeaderBytes, new Uint8Array(0), coseSign1.payload];
|
|
8400
|
-
const sigInput = decoder.encode(sigStructure);
|
|
8444
|
+
const sigInput = new Uint8Array(decoder.encode(sigStructure));
|
|
8401
8445
|
const ok = await crypto.subtle.verify(
|
|
8402
8446
|
{ name: "ECDSA", hash: hashAlg },
|
|
8403
8447
|
publicKey,
|
|
@@ -8936,32 +8980,74 @@ var SdJwtParser = class {
|
|
|
8936
8980
|
return invalidResult(`Unsupported algorithm: ${alg}`);
|
|
8937
8981
|
}
|
|
8938
8982
|
const x5cArray = header.x5c;
|
|
8939
|
-
|
|
8940
|
-
throw new MalformedCredentialError("Missing or invalid x5c in JWT header");
|
|
8941
|
-
}
|
|
8983
|
+
const hasX5c = Array.isArray(x5cArray) && x5cArray.length > 0 && typeof x5cArray[0] === "string";
|
|
8942
8984
|
let issuerKey;
|
|
8943
8985
|
let issuerCertBytes;
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8986
|
+
if (hasX5c) {
|
|
8987
|
+
try {
|
|
8988
|
+
issuerKey = await importX509(derToPem2(x5cArray[0]), alg);
|
|
8989
|
+
issuerCertBytes = base64ToBytes(x5cArray[0]);
|
|
8990
|
+
} catch (err) {
|
|
8991
|
+
if (err instanceof MalformedCredentialError) throw err;
|
|
8992
|
+
throw new MalformedCredentialError("Failed to import issuer certificate");
|
|
8993
|
+
}
|
|
8994
|
+
} else if (options.trustedIssuerJwks && options.trustedIssuerJwks.length > 0) {
|
|
8995
|
+
issuerKey = void 0;
|
|
8996
|
+
} else {
|
|
8997
|
+
throw new MalformedCredentialError("Missing or invalid x5c in JWT header");
|
|
8950
8998
|
}
|
|
8951
8999
|
let payload;
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
9000
|
+
if (issuerKey) {
|
|
9001
|
+
try {
|
|
9002
|
+
const result2 = await jwtVerify(parts.jwt, issuerKey, { algorithms: allowedAlgorithms });
|
|
9003
|
+
payload = result2.payload;
|
|
9004
|
+
} catch {
|
|
9005
|
+
return invalidResult("Issuer JWT signature verification failed");
|
|
9006
|
+
}
|
|
9007
|
+
} else {
|
|
9008
|
+
const jwks = options.trustedIssuerJwks;
|
|
9009
|
+
const headerKid = header["kid"];
|
|
9010
|
+
let candidates;
|
|
9011
|
+
if (typeof headerKid === "string") {
|
|
9012
|
+
const matched = jwks.find((k) => k["kid"] === headerKid);
|
|
9013
|
+
if (!matched) {
|
|
9014
|
+
return invalidResult(
|
|
9015
|
+
`No matching JWK found in trustedIssuerJwks for kid=${headerKid}`
|
|
9016
|
+
);
|
|
9017
|
+
}
|
|
9018
|
+
candidates = [matched];
|
|
9019
|
+
} else {
|
|
9020
|
+
candidates = jwks;
|
|
9021
|
+
}
|
|
9022
|
+
let verifiedPayload;
|
|
9023
|
+
for (const jwk of candidates) {
|
|
9024
|
+
let candidateKey;
|
|
9025
|
+
try {
|
|
9026
|
+
candidateKey = await importJWK(jwk, alg);
|
|
9027
|
+
} catch {
|
|
9028
|
+
continue;
|
|
9029
|
+
}
|
|
9030
|
+
try {
|
|
9031
|
+
const result2 = await jwtVerify(parts.jwt, candidateKey, {
|
|
9032
|
+
algorithms: allowedAlgorithms
|
|
9033
|
+
});
|
|
9034
|
+
verifiedPayload = result2.payload;
|
|
9035
|
+
break;
|
|
9036
|
+
} catch {
|
|
9037
|
+
}
|
|
9038
|
+
}
|
|
9039
|
+
if (!verifiedPayload) {
|
|
9040
|
+
return invalidResult("Issuer JWT signature verification failed");
|
|
9041
|
+
}
|
|
9042
|
+
payload = verifiedPayload;
|
|
8957
9043
|
}
|
|
8958
9044
|
const issuerString = typeof payload["iss"] === "string" ? payload["iss"] : "";
|
|
8959
9045
|
const issuerInfo = {
|
|
8960
|
-
certificate: issuerCertBytes,
|
|
9046
|
+
certificate: issuerCertBytes ?? new Uint8Array(),
|
|
8961
9047
|
country: extractCountryHint(issuerString)
|
|
8962
9048
|
};
|
|
8963
9049
|
let trustResult;
|
|
8964
|
-
if (options.trustStore) {
|
|
9050
|
+
if (issuerCertBytes === void 0) ; else if (options.trustStore) {
|
|
8965
9051
|
const { TrustEvaluator: TrustEvaluator2 } = await Promise.resolve().then(() => (init_TrustEvaluator(), TrustEvaluator_exports));
|
|
8966
9052
|
const evaluator = new TrustEvaluator2({
|
|
8967
9053
|
trustStore: options.trustStore,
|
|
@@ -8981,24 +9067,56 @@ var SdJwtParser = class {
|
|
|
8981
9067
|
);
|
|
8982
9068
|
}
|
|
8983
9069
|
} else {
|
|
8984
|
-
const isTrusted = options.trustedCertificates.some(
|
|
9070
|
+
const isTrusted = options.trustedCertificates.some(
|
|
9071
|
+
(trusted) => bytesEqual4(trusted, issuerCertBytes)
|
|
9072
|
+
);
|
|
8985
9073
|
if (!isTrusted) {
|
|
8986
9074
|
return invalidResult("Issuer certificate is not trusted");
|
|
8987
9075
|
}
|
|
8988
9076
|
}
|
|
8989
9077
|
}
|
|
8990
|
-
const sdArray = payload["_sd"];
|
|
8991
9078
|
if (parts.disclosures.length > 0) {
|
|
8992
|
-
|
|
8993
|
-
return invalidResult("Disclosures present but no _sd array in issuer JWT");
|
|
8994
|
-
}
|
|
9079
|
+
const decodedDisclosures = [];
|
|
8995
9080
|
for (const disclosure of parts.disclosures) {
|
|
9081
|
+
let parsed;
|
|
9082
|
+
try {
|
|
9083
|
+
parsed = JSON.parse(new TextDecoder().decode(base64ToBytes(disclosure)));
|
|
9084
|
+
} catch {
|
|
9085
|
+
return invalidResult("Disclosure is not valid base64url-encoded JSON");
|
|
9086
|
+
}
|
|
9087
|
+
if (!Array.isArray(parsed) || parsed.length !== 2 && parsed.length !== 3) {
|
|
9088
|
+
return invalidResult("Disclosure must be a 2- or 3-element array");
|
|
9089
|
+
}
|
|
8996
9090
|
const hashBytes = await sha256Hasher(disclosure);
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9091
|
+
decodedDisclosures.push({
|
|
9092
|
+
raw: disclosure,
|
|
9093
|
+
hash: bytesToBase64url(hashBytes),
|
|
9094
|
+
length: parsed.length,
|
|
9095
|
+
value: parsed.length === 3 ? parsed[2] : parsed[1]
|
|
9096
|
+
});
|
|
9097
|
+
}
|
|
9098
|
+
const seed = collectAnchorHashes(payload);
|
|
9099
|
+
const kvHashes = seed.kvHashes;
|
|
9100
|
+
const arrayElemHashes = seed.arrayElemHashes;
|
|
9101
|
+
const remaining = [...decodedDisclosures];
|
|
9102
|
+
for (; ; ) {
|
|
9103
|
+
const anchored = [];
|
|
9104
|
+
for (const d of remaining) {
|
|
9105
|
+
const valid = d.length === 3 ? kvHashes.has(d.hash) : arrayElemHashes.has(d.hash);
|
|
9106
|
+
if (valid) anchored.push(d);
|
|
9107
|
+
}
|
|
9108
|
+
if (anchored.length === 0) break;
|
|
9109
|
+
for (const a of anchored) {
|
|
9110
|
+
const sub = collectAnchorHashes(a.value);
|
|
9111
|
+
for (const h of sub.kvHashes) kvHashes.add(h);
|
|
9112
|
+
for (const h of sub.arrayElemHashes) arrayElemHashes.add(h);
|
|
9113
|
+
const idx = remaining.indexOf(a);
|
|
9114
|
+
if (idx >= 0) remaining.splice(idx, 1);
|
|
9000
9115
|
}
|
|
9001
9116
|
}
|
|
9117
|
+
if (remaining.length > 0) {
|
|
9118
|
+
return invalidResult("Disclosure hash mismatch");
|
|
9119
|
+
}
|
|
9002
9120
|
}
|
|
9003
9121
|
if (parts.kbJwt) {
|
|
9004
9122
|
const cnf = payload["cnf"];
|
|
@@ -9061,6 +9179,35 @@ var SdJwtParser = class {
|
|
|
9061
9179
|
return result;
|
|
9062
9180
|
}
|
|
9063
9181
|
};
|
|
9182
|
+
function collectAnchorHashes(node) {
|
|
9183
|
+
const kvHashes = /* @__PURE__ */ new Set();
|
|
9184
|
+
const arrayElemHashes = /* @__PURE__ */ new Set();
|
|
9185
|
+
const visit = (value) => {
|
|
9186
|
+
if (Array.isArray(value)) {
|
|
9187
|
+
for (const item of value) {
|
|
9188
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
9189
|
+
const obj = item;
|
|
9190
|
+
const keys = Object.keys(obj);
|
|
9191
|
+
if (keys.length === 1 && keys[0] === "..." && typeof obj["..."] === "string") {
|
|
9192
|
+
arrayElemHashes.add(obj["..."]);
|
|
9193
|
+
continue;
|
|
9194
|
+
}
|
|
9195
|
+
}
|
|
9196
|
+
visit(item);
|
|
9197
|
+
}
|
|
9198
|
+
} else if (value && typeof value === "object") {
|
|
9199
|
+
for (const [k, v] of Object.entries(value)) {
|
|
9200
|
+
if (k === "_sd" && Array.isArray(v)) {
|
|
9201
|
+
for (const h of v) if (typeof h === "string") kvHashes.add(h);
|
|
9202
|
+
continue;
|
|
9203
|
+
}
|
|
9204
|
+
visit(v);
|
|
9205
|
+
}
|
|
9206
|
+
}
|
|
9207
|
+
};
|
|
9208
|
+
visit(node);
|
|
9209
|
+
return { kvHashes, arrayElemHashes };
|
|
9210
|
+
}
|
|
9064
9211
|
|
|
9065
9212
|
// src/presentation.ts
|
|
9066
9213
|
var parsers = [new SdJwtParser(), new MdocParser()];
|
|
@@ -9800,7 +9947,7 @@ function equalBytes(a, b) {
|
|
|
9800
9947
|
|
|
9801
9948
|
// src/index.ts
|
|
9802
9949
|
init_errors();
|
|
9803
|
-
var VERSION = "0.
|
|
9950
|
+
var VERSION = "0.8.0";
|
|
9804
9951
|
/*! Bundled license information:
|
|
9805
9952
|
|
|
9806
9953
|
pvtsutils/build/index.es.js:
|