@openeudi/openid4vp 0.12.0 → 0.13.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 +51 -3
- package/dist/index.cjs +620 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -7
- package/dist/index.d.ts +112 -7
- package/dist/index.js +621 -86
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -45,7 +45,7 @@ var init_reflect_polyfill = __esm({
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
// src/errors.ts
|
|
48
|
-
exports.InvalidSignatureError = void 0; exports.ExpiredCredentialError = void 0; exports.UnsupportedFormatError = void 0; exports.MalformedCredentialError = void 0; exports.NonceValidationError = void 0; exports.HaipValidationError = void 0; exports.OpenID4VPError = void 0; exports.TrustAnchorNotFoundError = void 0; exports.CertificateChainError = void 0; exports.RevokedCertificateError = void 0; exports.RevocationCheckFailedError = void 0; exports.LotlFetchError = void 0; exports.LotlSignatureError = void 0; exports.LotlConfigurationError = void 0; exports.SignedRequestBuildError = void 0; exports.UnsupportedJweError = void 0; exports.DecryptionFailedError = void 0; exports.MissingDecryptionKeyError = void 0; exports.MissingVerifierEncryptionKeyError = void 0; exports.MultipleCredentialsNotSupportedError = void 0;
|
|
48
|
+
exports.InvalidSignatureError = void 0; exports.ExpiredCredentialError = void 0; exports.UnsupportedFormatError = void 0; exports.MalformedCredentialError = void 0; exports.NonceValidationError = void 0; exports.HaipValidationError = void 0; exports.OpenID4VPError = void 0; exports.TrustAnchorNotFoundError = void 0; exports.CertificateChainError = void 0; exports.RevokedCertificateError = void 0; exports.RevocationCheckFailedError = void 0; exports.LotlFetchError = void 0; exports.LotlSignatureError = void 0; exports.LotlConfigurationError = void 0; exports.SignedRequestBuildError = void 0; exports.UnsupportedJweError = void 0; exports.DecryptionFailedError = void 0; exports.MissingDecryptionKeyError = void 0; exports.MissingVerifierEncryptionKeyError = void 0; exports.MultipleCredentialsNotSupportedError = void 0; exports.GuardedFetchError = void 0;
|
|
49
49
|
var init_errors = __esm({
|
|
50
50
|
"src/errors.ts"() {
|
|
51
51
|
init_reflect_polyfill();
|
|
@@ -192,6 +192,16 @@ var init_errors = __esm({
|
|
|
192
192
|
this.presentationCount = presentationCount;
|
|
193
193
|
}
|
|
194
194
|
};
|
|
195
|
+
exports.GuardedFetchError = class extends exports.OpenID4VPError {
|
|
196
|
+
code = "guarded_fetch_rejected";
|
|
197
|
+
reason;
|
|
198
|
+
url;
|
|
199
|
+
constructor(reason, message, options) {
|
|
200
|
+
super(message, { cause: options.cause });
|
|
201
|
+
this.reason = reason;
|
|
202
|
+
this.url = options.url;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
195
205
|
}
|
|
196
206
|
});
|
|
197
207
|
|
|
@@ -6588,7 +6598,7 @@ function dnIsUnder(childDn, subtreeDn) {
|
|
|
6588
6598
|
}
|
|
6589
6599
|
return true;
|
|
6590
6600
|
}
|
|
6591
|
-
var NAME_CONSTRAINTS_OID, DEFAULT_ALGORITHMS, ChainBuilder;
|
|
6601
|
+
var NAME_CONSTRAINTS_OID, ISO_18013_5_DS_EKU, DEFAULT_MAX_CHAIN_LENGTH, DEFAULT_MAX_SIGNATURE_CHECKS, searchAborts, DEFAULT_ALGORITHMS, ChainBuilder;
|
|
6592
6602
|
var init_ChainBuilder = __esm({
|
|
6593
6603
|
"src/trust/ChainBuilder.ts"() {
|
|
6594
6604
|
init_reflect_polyfill();
|
|
@@ -6597,6 +6607,10 @@ var init_ChainBuilder = __esm({
|
|
|
6597
6607
|
init_errors();
|
|
6598
6608
|
init_x509_utils();
|
|
6599
6609
|
NAME_CONSTRAINTS_OID = "2.5.29.30";
|
|
6610
|
+
ISO_18013_5_DS_EKU = "1.0.18013.5.1.2";
|
|
6611
|
+
DEFAULT_MAX_CHAIN_LENGTH = 5;
|
|
6612
|
+
DEFAULT_MAX_SIGNATURE_CHECKS = 32;
|
|
6613
|
+
searchAborts = /* @__PURE__ */ new WeakSet();
|
|
6600
6614
|
DEFAULT_ALGORITHMS = [
|
|
6601
6615
|
"ES256",
|
|
6602
6616
|
"ES384",
|
|
@@ -6619,62 +6633,130 @@ var init_ChainBuilder = __esm({
|
|
|
6619
6633
|
* optionally using additional intermediates. Returns the ordered chain
|
|
6620
6634
|
* (leaf → anchor). Throws `CertificateChainError` on any validation
|
|
6621
6635
|
* failure.
|
|
6636
|
+
*
|
|
6637
|
+
* Trust is by presence in the anchor set and is established ONLY by (a) the
|
|
6638
|
+
* leaf being byte-identical to an anchor, or (b) a verified signature by an
|
|
6639
|
+
* anchor's key on the last certificate of the path. Subject/Issuer DN
|
|
6640
|
+
* equality is used to select candidates, never to conclude trust
|
|
6641
|
+
* (GHSA-4c2f-96cf-f5fc).
|
|
6642
|
+
*
|
|
6643
|
+
* The search is depth-first and backtracks: when several intermediates share
|
|
6644
|
+
* a Subject DN — a re-issued certificate next to its expired predecessor, or
|
|
6645
|
+
* cross-signed copies under different roots — a candidate that fails a check
|
|
6646
|
+
* or leads nowhere does not end the search. The result therefore does not
|
|
6647
|
+
* depend on the order of `intermediates`. Work is bounded by
|
|
6648
|
+
* `maxChainLength` and `maxSignatureChecks`.
|
|
6622
6649
|
*/
|
|
6623
|
-
async build(leaf, anchors, intermediates = []) {
|
|
6624
|
-
const
|
|
6650
|
+
async build(leaf, anchors, intermediates = [], context = {}) {
|
|
6651
|
+
const search = {
|
|
6652
|
+
remaining: this.opts.maxSignatureChecks ?? DEFAULT_MAX_SIGNATURE_CHECKS
|
|
6653
|
+
};
|
|
6654
|
+
let lastFailure;
|
|
6625
6655
|
for (const anchor of anchors) {
|
|
6656
|
+
search.specific = void 0;
|
|
6657
|
+
search.fallback = void 0;
|
|
6626
6658
|
try {
|
|
6627
|
-
|
|
6659
|
+
const chain = await this.tryBuild(leaf, anchor, intermediates, context, search);
|
|
6660
|
+
if (chain) return chain;
|
|
6661
|
+
lastFailure = search.specific ?? search.fallback;
|
|
6628
6662
|
} catch (err) {
|
|
6629
|
-
|
|
6663
|
+
if (searchAborts.has(err)) throw err;
|
|
6664
|
+
lastFailure = err;
|
|
6630
6665
|
}
|
|
6631
6666
|
}
|
|
6632
|
-
|
|
6633
|
-
if (last instanceof exports.CertificateChainError) throw last;
|
|
6667
|
+
if (lastFailure instanceof exports.CertificateChainError) throw lastFailure;
|
|
6634
6668
|
throw new exports.CertificateChainError("no valid chain to any anchor", {
|
|
6635
6669
|
reason: "signature",
|
|
6636
|
-
cause:
|
|
6670
|
+
cause: lastFailure
|
|
6637
6671
|
});
|
|
6638
6672
|
}
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
this.
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6673
|
+
/** Search for a path from `leaf` that closes at `anchor`. */
|
|
6674
|
+
async tryBuild(leaf, anchor, intermediates, context, search) {
|
|
6675
|
+
this.checkPerCert(leaf, context);
|
|
6676
|
+
if (certificatesEqual(leaf, anchor)) {
|
|
6677
|
+
this.checkLeafKeyUsage(leaf, "generic");
|
|
6678
|
+
return [leaf];
|
|
6679
|
+
}
|
|
6680
|
+
const profile = this.opts.profile ?? "generic";
|
|
6681
|
+
this.checkLeafKeyUsage(leaf, profile);
|
|
6682
|
+
this.checkLeafExtendedKeyUsage(leaf, profile);
|
|
6683
|
+
const pool = intermediates.filter(
|
|
6684
|
+
(c) => !certificatesEqual(c, anchor) && !certificatesEqual(c, leaf)
|
|
6685
|
+
);
|
|
6686
|
+
return this.extendPath([leaf], anchor, pool, context, search);
|
|
6687
|
+
}
|
|
6688
|
+
/**
|
|
6689
|
+
* Try to complete `chain` (leaf first) to `anchor`. Returns the completed
|
|
6690
|
+
* chain, or `undefined` when no extension of this prefix works — failures
|
|
6691
|
+
* are recorded on `search` so the caller can report the most specific one.
|
|
6692
|
+
* Throws only to abandon the whole search (signature budget exhausted).
|
|
6693
|
+
*/
|
|
6694
|
+
async extendPath(chain, anchor, pool, context, search) {
|
|
6695
|
+
const leaf = chain[0];
|
|
6696
|
+
const current = chain[chain.length - 1];
|
|
6697
|
+
const maxLength = this.opts.maxChainLength ?? DEFAULT_MAX_CHAIN_LENGTH;
|
|
6698
|
+
if (chain.length + 1 > maxLength) {
|
|
6699
|
+
search.specific = new exports.CertificateChainError(
|
|
6700
|
+
`chain from ${leaf.subject} exceeds the maximum length of ${maxLength} certificates`,
|
|
6701
|
+
{ reason: "path_length" }
|
|
6702
|
+
);
|
|
6703
|
+
return void 0;
|
|
6704
|
+
}
|
|
6705
|
+
const nonLeafDepth = chain.length - 1;
|
|
6706
|
+
if (current.issuer === anchor.subject) {
|
|
6707
|
+
try {
|
|
6708
|
+
await this.closeAtAnchor(current, anchor, nonLeafDepth, context, search);
|
|
6709
|
+
const closed = [...chain, anchor];
|
|
6710
|
+
this.checkNameConstraints(leaf, closed.slice(1));
|
|
6711
|
+
return closed;
|
|
6712
|
+
} catch (err) {
|
|
6713
|
+
if (searchAborts.has(err)) throw err;
|
|
6714
|
+
search.specific = err;
|
|
6661
6715
|
}
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6716
|
+
}
|
|
6717
|
+
const candidates = pool.filter(
|
|
6718
|
+
(c) => c.subject === current.issuer && !chain.some((x) => certificatesEqual(x, c))
|
|
6719
|
+
);
|
|
6720
|
+
let verified = 0;
|
|
6721
|
+
for (const candidate of candidates) {
|
|
6722
|
+
if (!await this.signatureVerifies(current, candidate, search)) continue;
|
|
6723
|
+
verified++;
|
|
6724
|
+
try {
|
|
6725
|
+
this.checkPerCert(candidate, context);
|
|
6726
|
+
this.checkCaAndPathLen(candidate, nonLeafDepth);
|
|
6727
|
+
this.checkAkiSkiMatch(current, candidate);
|
|
6728
|
+
} catch (err) {
|
|
6729
|
+
search.specific = err;
|
|
6730
|
+
continue;
|
|
6731
|
+
}
|
|
6732
|
+
const found = await this.extendPath([...chain, candidate], anchor, pool, context, search);
|
|
6733
|
+
if (found) return found;
|
|
6734
|
+
}
|
|
6735
|
+
search.fallback = new exports.CertificateChainError(
|
|
6736
|
+
candidates.length > 0 && verified === 0 ? `signature verification failed for ${current.subject}` : `no issuer certificate found for ${current.subject}`,
|
|
6737
|
+
{ reason: "signature" }
|
|
6738
|
+
);
|
|
6739
|
+
return void 0;
|
|
6740
|
+
}
|
|
6741
|
+
async closeAtAnchor(current, anchor, nonLeafDepth, context, search) {
|
|
6742
|
+
this.checkPerCert(anchor, context);
|
|
6743
|
+
this.checkCaAndPathLen(anchor, nonLeafDepth);
|
|
6744
|
+
this.checkAkiSkiMatch(current, anchor);
|
|
6745
|
+
this.spendSignatureCheck(search);
|
|
6746
|
+
await this.verifySignature(current, anchor);
|
|
6747
|
+
}
|
|
6748
|
+
/** Charge one signature verification to the search, or abandon it. */
|
|
6749
|
+
spendSignatureCheck(search) {
|
|
6750
|
+
if (search.remaining <= 0) {
|
|
6751
|
+
const limit = this.opts.maxSignatureChecks ?? DEFAULT_MAX_SIGNATURE_CHECKS;
|
|
6752
|
+
const err = new exports.CertificateChainError(
|
|
6753
|
+
`certificate path search exceeded ${limit} signature checks`,
|
|
6754
|
+
{ reason: "path_length" }
|
|
6674
6755
|
);
|
|
6756
|
+
searchAborts.add(err);
|
|
6757
|
+
throw err;
|
|
6675
6758
|
}
|
|
6676
|
-
|
|
6677
|
-
return chain;
|
|
6759
|
+
search.remaining--;
|
|
6678
6760
|
}
|
|
6679
6761
|
/**
|
|
6680
6762
|
* Enforce nameConstraints asserted by any CA above the leaf against the
|
|
@@ -6723,7 +6805,7 @@ var init_ChainBuilder = __esm({
|
|
|
6723
6805
|
}
|
|
6724
6806
|
}
|
|
6725
6807
|
checkCaAndPathLen(cert, nonLeafDepth) {
|
|
6726
|
-
this.checkCaKeyUsage(cert);
|
|
6808
|
+
this.checkCaKeyUsage(cert, this.opts.profile ?? "generic");
|
|
6727
6809
|
const bc = cert.getExtension(x509.BasicConstraintsExtension);
|
|
6728
6810
|
if (!bc || !bc.ca) {
|
|
6729
6811
|
throw new exports.CertificateChainError(
|
|
@@ -6738,9 +6820,17 @@ var init_ChainBuilder = __esm({
|
|
|
6738
6820
|
);
|
|
6739
6821
|
}
|
|
6740
6822
|
}
|
|
6741
|
-
checkLeafKeyUsage(leaf) {
|
|
6823
|
+
checkLeafKeyUsage(leaf, profile) {
|
|
6742
6824
|
const ext = leaf.getExtension(x509.KeyUsagesExtension);
|
|
6743
|
-
if (!ext)
|
|
6825
|
+
if (!ext) {
|
|
6826
|
+
if (profile === "iso18013-5") {
|
|
6827
|
+
throw new exports.CertificateChainError(
|
|
6828
|
+
`document signer ${leaf.subject} has no keyUsage extension (ISO 18013-5 requires digitalSignature)`,
|
|
6829
|
+
{ reason: "key_usage" }
|
|
6830
|
+
);
|
|
6831
|
+
}
|
|
6832
|
+
return;
|
|
6833
|
+
}
|
|
6744
6834
|
if ((ext.usages & x509.KeyUsageFlags.digitalSignature) === 0) {
|
|
6745
6835
|
throw new exports.CertificateChainError(
|
|
6746
6836
|
`leaf ${leaf.subject} does not assert digitalSignature key usage`,
|
|
@@ -6748,9 +6838,28 @@ var init_ChainBuilder = __esm({
|
|
|
6748
6838
|
);
|
|
6749
6839
|
}
|
|
6750
6840
|
}
|
|
6751
|
-
|
|
6841
|
+
checkLeafExtendedKeyUsage(leaf, profile) {
|
|
6842
|
+
if (profile !== "iso18013-5") return;
|
|
6843
|
+
const ext = leaf.getExtension(x509.ExtendedKeyUsageExtension);
|
|
6844
|
+
const usages = (ext?.usages ?? []).map(String);
|
|
6845
|
+
if (!usages.includes(ISO_18013_5_DS_EKU)) {
|
|
6846
|
+
throw new exports.CertificateChainError(
|
|
6847
|
+
`document signer ${leaf.subject} lacks extended key usage ${ISO_18013_5_DS_EKU} (id-mdl-kp-mdlDS)`,
|
|
6848
|
+
{ reason: "extended_key_usage" }
|
|
6849
|
+
);
|
|
6850
|
+
}
|
|
6851
|
+
}
|
|
6852
|
+
checkCaKeyUsage(cert, profile) {
|
|
6752
6853
|
const ext = cert.getExtension(x509.KeyUsagesExtension);
|
|
6753
|
-
if (!ext)
|
|
6854
|
+
if (!ext) {
|
|
6855
|
+
if (profile === "iso18013-5") {
|
|
6856
|
+
throw new exports.CertificateChainError(
|
|
6857
|
+
`CA ${cert.subject} has no keyUsage extension (ISO 18013-5 requires keyCertSign)`,
|
|
6858
|
+
{ reason: "key_usage" }
|
|
6859
|
+
);
|
|
6860
|
+
}
|
|
6861
|
+
return;
|
|
6862
|
+
}
|
|
6754
6863
|
if ((ext.usages & x509.KeyUsageFlags.keyCertSign) === 0) {
|
|
6755
6864
|
throw new exports.CertificateChainError(
|
|
6756
6865
|
`CA ${cert.subject} does not assert keyCertSign key usage`,
|
|
@@ -6771,30 +6880,46 @@ var init_ChainBuilder = __esm({
|
|
|
6771
6880
|
);
|
|
6772
6881
|
}
|
|
6773
6882
|
}
|
|
6883
|
+
/**
|
|
6884
|
+
* Signature check used to select among DN-matching candidates. Never throws
|
|
6885
|
+
* on a bad signature; throws only when the search budget is exhausted.
|
|
6886
|
+
*/
|
|
6887
|
+
async signatureVerifies(child, issuer, search) {
|
|
6888
|
+
this.spendSignatureCheck(search);
|
|
6889
|
+
try {
|
|
6890
|
+
return await child.verify({ publicKey: issuer.publicKey, signatureOnly: true });
|
|
6891
|
+
} catch {
|
|
6892
|
+
return false;
|
|
6893
|
+
}
|
|
6894
|
+
}
|
|
6774
6895
|
async verifySignature(child, issuer) {
|
|
6775
6896
|
const ok = await child.verify({ publicKey: issuer.publicKey, signatureOnly: true });
|
|
6776
6897
|
if (!ok) {
|
|
6777
6898
|
throw new exports.CertificateChainError(`signature verification failed for ${child.subject}`, { reason: "signature" });
|
|
6778
6899
|
}
|
|
6779
6900
|
}
|
|
6780
|
-
checkPerCert(cert) {
|
|
6781
|
-
this.
|
|
6901
|
+
checkPerCert(cert, context = {}) {
|
|
6902
|
+
const now = (this.opts.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
6903
|
+
this.checkValidity(cert, now, "");
|
|
6904
|
+
if (context.issuedAt) {
|
|
6905
|
+
this.checkValidity(cert, context.issuedAt, ` at issuance time ${context.issuedAt.toISOString()}`);
|
|
6906
|
+
}
|
|
6782
6907
|
this.checkAlgorithm(cert);
|
|
6783
6908
|
}
|
|
6784
|
-
checkValidity(cert) {
|
|
6785
|
-
const
|
|
6909
|
+
checkValidity(cert, at, label) {
|
|
6910
|
+
const time = at.getTime();
|
|
6786
6911
|
const skewMs = (this.opts.clockSkewTolerance ?? 60) * 1e3;
|
|
6787
6912
|
const notBefore = cert.notBefore.getTime();
|
|
6788
6913
|
const notAfter = cert.notAfter.getTime();
|
|
6789
|
-
if (
|
|
6914
|
+
if (time + skewMs < notBefore) {
|
|
6790
6915
|
throw new exports.CertificateChainError(
|
|
6791
|
-
`certificate ${cert.subject} not yet valid (notBefore=${cert.notBefore.toISOString()})`,
|
|
6916
|
+
`certificate ${cert.subject} not yet valid${label} (notBefore=${cert.notBefore.toISOString()})`,
|
|
6792
6917
|
{ reason: "validity" }
|
|
6793
6918
|
);
|
|
6794
6919
|
}
|
|
6795
|
-
if (
|
|
6920
|
+
if (time - skewMs > notAfter) {
|
|
6796
6921
|
throw new exports.CertificateChainError(
|
|
6797
|
-
`certificate ${cert.subject} expired (notAfter=${cert.notAfter.toISOString()})`,
|
|
6922
|
+
`certificate ${cert.subject} expired${label} (notAfter=${cert.notAfter.toISOString()})`,
|
|
6798
6923
|
{ reason: "validity" }
|
|
6799
6924
|
);
|
|
6800
6925
|
}
|
|
@@ -6841,6 +6966,349 @@ var init_ecdsa_util = __esm({
|
|
|
6841
6966
|
}
|
|
6842
6967
|
});
|
|
6843
6968
|
|
|
6969
|
+
// src/http/ip-address.ts
|
|
6970
|
+
function parseIPv4(input) {
|
|
6971
|
+
const parts = input.split(".");
|
|
6972
|
+
if (parts.length !== 4) return void 0;
|
|
6973
|
+
const bytes = [];
|
|
6974
|
+
for (const part of parts) {
|
|
6975
|
+
if (!/^\d{1,3}$/.test(part)) return void 0;
|
|
6976
|
+
const value = Number(part);
|
|
6977
|
+
if (value > 255) return void 0;
|
|
6978
|
+
bytes.push(value);
|
|
6979
|
+
}
|
|
6980
|
+
return bytes;
|
|
6981
|
+
}
|
|
6982
|
+
function parseIPv6(input) {
|
|
6983
|
+
const address = input.split("%")[0];
|
|
6984
|
+
if (!address.includes(":")) return void 0;
|
|
6985
|
+
let head = address;
|
|
6986
|
+
let embeddedV4;
|
|
6987
|
+
const lastColon = address.lastIndexOf(":");
|
|
6988
|
+
const tail = address.slice(lastColon + 1);
|
|
6989
|
+
if (tail.includes(".")) {
|
|
6990
|
+
embeddedV4 = parseIPv4(tail);
|
|
6991
|
+
if (!embeddedV4) return void 0;
|
|
6992
|
+
head = address.slice(0, lastColon + 1) + "0:0";
|
|
6993
|
+
}
|
|
6994
|
+
const doubleColon = head.split("::");
|
|
6995
|
+
if (doubleColon.length > 2) return void 0;
|
|
6996
|
+
const toGroups = (s) => s === "" ? [] : s.split(":");
|
|
6997
|
+
const left = toGroups(doubleColon[0]);
|
|
6998
|
+
const right = doubleColon.length === 2 ? toGroups(doubleColon[1]) : [];
|
|
6999
|
+
const missing = 8 - left.length - right.length;
|
|
7000
|
+
if (doubleColon.length === 2 ? missing < 1 : missing !== 0) return void 0;
|
|
7001
|
+
const groups = [...left, ...Array(doubleColon.length === 2 ? missing : 0).fill("0"), ...right];
|
|
7002
|
+
const bytes = [];
|
|
7003
|
+
for (const group of groups) {
|
|
7004
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(group)) return void 0;
|
|
7005
|
+
const value = parseInt(group, 16);
|
|
7006
|
+
bytes.push(value >> 8, value & 255);
|
|
7007
|
+
}
|
|
7008
|
+
if (embeddedV4) bytes.splice(12, 4, ...embeddedV4);
|
|
7009
|
+
return bytes;
|
|
7010
|
+
}
|
|
7011
|
+
function inPrefix(bytes, prefix, bits) {
|
|
7012
|
+
for (let i = 0; i < bits; i++) {
|
|
7013
|
+
const byte = i >> 3;
|
|
7014
|
+
const mask = 128 >> (i & 7);
|
|
7015
|
+
if ((bytes[byte] & mask) !== ((prefix[byte] ?? 0) & mask)) return false;
|
|
7016
|
+
}
|
|
7017
|
+
return true;
|
|
7018
|
+
}
|
|
7019
|
+
function isPublicV4(bytes) {
|
|
7020
|
+
return !BLOCKED_V4.some(([prefix, bits]) => inPrefix(bytes, prefix, bits));
|
|
7021
|
+
}
|
|
7022
|
+
function isPublicV6(bytes) {
|
|
7023
|
+
const first10Zero = bytes.slice(0, 10).every((b) => b === 0);
|
|
7024
|
+
if (first10Zero && bytes[10] === 0 && bytes[11] === 0) {
|
|
7025
|
+
const v4 = bytes.slice(12);
|
|
7026
|
+
if (v4.slice(0, 3).every((b) => b === 0)) return false;
|
|
7027
|
+
return isPublicV4(v4);
|
|
7028
|
+
}
|
|
7029
|
+
if (first10Zero && bytes[10] === 255 && bytes[11] === 255) return isPublicV4(bytes.slice(12));
|
|
7030
|
+
if (inPrefix(bytes, [0, 100, 255, 155], 96)) return isPublicV4(bytes.slice(12));
|
|
7031
|
+
if (inPrefix(bytes, [32, 2], 16)) return isPublicV4(bytes.slice(2, 6));
|
|
7032
|
+
return !BLOCKED_V6.some(([prefix, bits]) => inPrefix(bytes, prefix, bits));
|
|
7033
|
+
}
|
|
7034
|
+
function isPublicIpAddress(ip) {
|
|
7035
|
+
const v4 = parseIPv4(ip);
|
|
7036
|
+
if (v4) return isPublicV4(v4);
|
|
7037
|
+
const v6 = parseIPv6(ip);
|
|
7038
|
+
if (v6) return isPublicV6(v6);
|
|
7039
|
+
return false;
|
|
7040
|
+
}
|
|
7041
|
+
function isIpLiteral(host) {
|
|
7042
|
+
return parseIPv4(host) !== void 0 || parseIPv6(host) !== void 0;
|
|
7043
|
+
}
|
|
7044
|
+
var BLOCKED_V4, BLOCKED_V6;
|
|
7045
|
+
var init_ip_address = __esm({
|
|
7046
|
+
"src/http/ip-address.ts"() {
|
|
7047
|
+
init_reflect_polyfill();
|
|
7048
|
+
BLOCKED_V4 = [
|
|
7049
|
+
[[0, 0, 0, 0], 8],
|
|
7050
|
+
[[10, 0, 0, 0], 8],
|
|
7051
|
+
[[100, 64, 0, 0], 10],
|
|
7052
|
+
[[127, 0, 0, 0], 8],
|
|
7053
|
+
[[169, 254, 0, 0], 16],
|
|
7054
|
+
[[172, 16, 0, 0], 12],
|
|
7055
|
+
[[192, 0, 0, 0], 24],
|
|
7056
|
+
[[192, 0, 2, 0], 24],
|
|
7057
|
+
[[192, 88, 99, 0], 24],
|
|
7058
|
+
[[192, 168, 0, 0], 16],
|
|
7059
|
+
[[198, 18, 0, 0], 15],
|
|
7060
|
+
[[198, 51, 100, 0], 24],
|
|
7061
|
+
[[203, 0, 113, 0], 24],
|
|
7062
|
+
[[224, 0, 0, 0], 4],
|
|
7063
|
+
[[240, 0, 0, 0], 4]
|
|
7064
|
+
];
|
|
7065
|
+
BLOCKED_V6 = [
|
|
7066
|
+
[[1, 0], 64],
|
|
7067
|
+
// 100::/64 discard-only
|
|
7068
|
+
[[32, 1, 0, 0], 32],
|
|
7069
|
+
// 2001::/32 Teredo (embeds an obfuscated IPv4)
|
|
7070
|
+
[[32, 1, 13, 184], 32],
|
|
7071
|
+
// 2001:db8::/32 documentation
|
|
7072
|
+
[[252], 7],
|
|
7073
|
+
// fc00::/7 unique local (incl. fd00:ec2::254 metadata)
|
|
7074
|
+
[[254, 128], 10],
|
|
7075
|
+
// fe80::/10 link-local
|
|
7076
|
+
[[254, 192], 10],
|
|
7077
|
+
// fec0::/10 deprecated site-local
|
|
7078
|
+
[[255], 8]
|
|
7079
|
+
// ff00::/8 multicast
|
|
7080
|
+
];
|
|
7081
|
+
}
|
|
7082
|
+
});
|
|
7083
|
+
|
|
7084
|
+
// src/http/guarded-fetch.ts
|
|
7085
|
+
function loadDns() {
|
|
7086
|
+
dnsModule ??= (async () => {
|
|
7087
|
+
try {
|
|
7088
|
+
const specifier = "node:dns";
|
|
7089
|
+
const mod = await import(
|
|
7090
|
+
/* webpackIgnore: true */
|
|
7091
|
+
/* @vite-ignore */
|
|
7092
|
+
specifier
|
|
7093
|
+
);
|
|
7094
|
+
return mod.promises ?? mod.default?.promises;
|
|
7095
|
+
} catch {
|
|
7096
|
+
return void 0;
|
|
7097
|
+
}
|
|
7098
|
+
})();
|
|
7099
|
+
return dnsModule;
|
|
7100
|
+
}
|
|
7101
|
+
function toError(err) {
|
|
7102
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
7103
|
+
}
|
|
7104
|
+
function createGuardedFetcher(options = {}) {
|
|
7105
|
+
const allowHttp = options.allowHttp ?? false;
|
|
7106
|
+
const allowPrivate = options.allowPrivateNetworks ?? false;
|
|
7107
|
+
const maxRedirects = options.maxRedirects ?? exports.GUARDED_FETCH_DEFAULTS.maxRedirects;
|
|
7108
|
+
const timeoutMs = options.timeoutMs ?? exports.GUARDED_FETCH_DEFAULTS.timeoutMs;
|
|
7109
|
+
const maxBytes = options.maxResponseBytes ?? exports.GUARDED_FETCH_DEFAULTS.maxResponseBytes;
|
|
7110
|
+
const lookup = options.lookup ?? defaultLookup;
|
|
7111
|
+
const transport = options.fetch ?? ((url, init) => globalThis.fetch(url, init));
|
|
7112
|
+
return async (input, init = {}) => {
|
|
7113
|
+
const controller = new AbortController();
|
|
7114
|
+
const timer = setTimeout(() => {
|
|
7115
|
+
controller.abort(
|
|
7116
|
+
new exports.GuardedFetchError("timeout", `request to ${input} exceeded ${timeoutMs} ms`, { url: input })
|
|
7117
|
+
);
|
|
7118
|
+
}, timeoutMs);
|
|
7119
|
+
const callerSignal = init.signal ?? void 0;
|
|
7120
|
+
const onCallerAbort = () => controller.abort(callerSignal?.reason);
|
|
7121
|
+
if (callerSignal?.aborted) onCallerAbort();
|
|
7122
|
+
else callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
7123
|
+
const aborted = new Promise((_, reject) => {
|
|
7124
|
+
const fail = () => reject(toError(controller.signal.reason));
|
|
7125
|
+
if (controller.signal.aborted) fail();
|
|
7126
|
+
else controller.signal.addEventListener("abort", fail, { once: true });
|
|
7127
|
+
});
|
|
7128
|
+
aborted.catch(() => {
|
|
7129
|
+
});
|
|
7130
|
+
const race = (p) => Promise.race([p, aborted]);
|
|
7131
|
+
try {
|
|
7132
|
+
let url = await race(validateTarget(input, void 0));
|
|
7133
|
+
let method = (init.method ?? "GET").toUpperCase();
|
|
7134
|
+
let body = init.body;
|
|
7135
|
+
const headers = new Headers(init.headers);
|
|
7136
|
+
for (let hop = 0; ; hop++) {
|
|
7137
|
+
const response = await race(
|
|
7138
|
+
transport(url.href, { ...init, method, body, headers, redirect: "manual", signal: controller.signal })
|
|
7139
|
+
);
|
|
7140
|
+
if (response.type === "opaqueredirect") {
|
|
7141
|
+
throw new exports.GuardedFetchError(
|
|
7142
|
+
"invalid_redirect",
|
|
7143
|
+
`redirect from ${url.href} cannot be inspected by this transport`,
|
|
7144
|
+
{ url: url.href }
|
|
7145
|
+
);
|
|
7146
|
+
}
|
|
7147
|
+
if (!REDIRECT_STATUSES.has(response.status)) {
|
|
7148
|
+
const bytes = await readBounded(response, url.href, maxBytes, race);
|
|
7149
|
+
return new Response(NULL_BODY_STATUSES.has(response.status) ? null : bytes, {
|
|
7150
|
+
status: response.status,
|
|
7151
|
+
statusText: response.statusText,
|
|
7152
|
+
headers: response.headers
|
|
7153
|
+
});
|
|
7154
|
+
}
|
|
7155
|
+
await response.body?.cancel().catch(() => {
|
|
7156
|
+
});
|
|
7157
|
+
if (hop >= maxRedirects) {
|
|
7158
|
+
throw new exports.GuardedFetchError(
|
|
7159
|
+
"too_many_redirects",
|
|
7160
|
+
`more than ${maxRedirects} redirects starting at ${input}`,
|
|
7161
|
+
{ url: url.href }
|
|
7162
|
+
);
|
|
7163
|
+
}
|
|
7164
|
+
const location = response.headers.get("location");
|
|
7165
|
+
if (!location) {
|
|
7166
|
+
throw new exports.GuardedFetchError(
|
|
7167
|
+
"invalid_redirect",
|
|
7168
|
+
`HTTP ${response.status} from ${url.href} has no Location header`,
|
|
7169
|
+
{ url: url.href }
|
|
7170
|
+
);
|
|
7171
|
+
}
|
|
7172
|
+
let next;
|
|
7173
|
+
try {
|
|
7174
|
+
next = new URL(location, url);
|
|
7175
|
+
} catch (err) {
|
|
7176
|
+
throw new exports.GuardedFetchError("invalid_redirect", `unparseable Location from ${url.href}`, {
|
|
7177
|
+
url: url.href,
|
|
7178
|
+
cause: toError(err)
|
|
7179
|
+
});
|
|
7180
|
+
}
|
|
7181
|
+
const nextUrl = await race(validateTarget(next.href, url));
|
|
7182
|
+
if (response.status === 303 || (response.status === 301 || response.status === 302) && method === "POST") {
|
|
7183
|
+
if (method !== "HEAD") method = "GET";
|
|
7184
|
+
body = void 0;
|
|
7185
|
+
for (const h of BODY_HEADERS) headers.delete(h);
|
|
7186
|
+
}
|
|
7187
|
+
if (nextUrl.origin !== url.origin) {
|
|
7188
|
+
for (const h of CROSS_ORIGIN_STRIPPED_HEADERS) headers.delete(h);
|
|
7189
|
+
}
|
|
7190
|
+
url = nextUrl;
|
|
7191
|
+
}
|
|
7192
|
+
} catch (err) {
|
|
7193
|
+
if (controller.signal.aborted) throw toError(controller.signal.reason);
|
|
7194
|
+
throw err;
|
|
7195
|
+
} finally {
|
|
7196
|
+
clearTimeout(timer);
|
|
7197
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
7198
|
+
}
|
|
7199
|
+
};
|
|
7200
|
+
async function validateTarget(raw, previous) {
|
|
7201
|
+
let url;
|
|
7202
|
+
try {
|
|
7203
|
+
url = new URL(raw);
|
|
7204
|
+
} catch (err) {
|
|
7205
|
+
throw new exports.GuardedFetchError("invalid_url", `not an absolute URL: ${raw}`, { url: raw, cause: toError(err) });
|
|
7206
|
+
}
|
|
7207
|
+
if (url.protocol !== "https:" && !(allowHttp && url.protocol === "http:")) {
|
|
7208
|
+
throw new exports.GuardedFetchError("insecure_scheme", `refusing ${url.protocol} URL ${url.href}: HTTPS is required`, {
|
|
7209
|
+
url: url.href
|
|
7210
|
+
});
|
|
7211
|
+
}
|
|
7212
|
+
if (previous?.protocol === "https:" && url.protocol !== "https:") {
|
|
7213
|
+
throw new exports.GuardedFetchError("insecure_scheme", `refusing https -> http redirect to ${url.href}`, {
|
|
7214
|
+
url: url.href
|
|
7215
|
+
});
|
|
7216
|
+
}
|
|
7217
|
+
if (allowPrivate) return url;
|
|
7218
|
+
const host = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
7219
|
+
if (host === "localhost" || host.endsWith(".localhost")) {
|
|
7220
|
+
throw new exports.GuardedFetchError("private_address", `refusing loopback host ${host}`, { url: url.href });
|
|
7221
|
+
}
|
|
7222
|
+
if (isIpLiteral(host)) {
|
|
7223
|
+
if (!isPublicIpAddress(host)) {
|
|
7224
|
+
throw new exports.GuardedFetchError("private_address", `refusing non-public address ${host}`, { url: url.href });
|
|
7225
|
+
}
|
|
7226
|
+
return url;
|
|
7227
|
+
}
|
|
7228
|
+
let addresses;
|
|
7229
|
+
try {
|
|
7230
|
+
addresses = await lookup(host);
|
|
7231
|
+
} catch (err) {
|
|
7232
|
+
throw new exports.GuardedFetchError("dns_resolution_failed", `could not resolve ${host}`, {
|
|
7233
|
+
url: url.href,
|
|
7234
|
+
cause: toError(err)
|
|
7235
|
+
});
|
|
7236
|
+
}
|
|
7237
|
+
if (addresses === void 0) return url;
|
|
7238
|
+
if (addresses.length === 0) {
|
|
7239
|
+
throw new exports.GuardedFetchError("dns_resolution_failed", `${host} resolved to no addresses`, {
|
|
7240
|
+
url: url.href
|
|
7241
|
+
});
|
|
7242
|
+
}
|
|
7243
|
+
const blocked = addresses.find((a) => !isPublicIpAddress(a));
|
|
7244
|
+
if (blocked !== void 0) {
|
|
7245
|
+
throw new exports.GuardedFetchError("private_address", `${host} resolves to non-public address ${blocked}`, {
|
|
7246
|
+
url: url.href
|
|
7247
|
+
});
|
|
7248
|
+
}
|
|
7249
|
+
return url;
|
|
7250
|
+
}
|
|
7251
|
+
}
|
|
7252
|
+
async function readBounded(response, url, maxBytes, race) {
|
|
7253
|
+
const tooLarge = () => new exports.GuardedFetchError("response_too_large", `response from ${url} exceeds ${maxBytes} bytes`, { url });
|
|
7254
|
+
const declared = response.headers.get("content-length");
|
|
7255
|
+
if (declared !== null && Number(declared) > maxBytes) {
|
|
7256
|
+
await response.body?.cancel().catch(() => {
|
|
7257
|
+
});
|
|
7258
|
+
throw tooLarge();
|
|
7259
|
+
}
|
|
7260
|
+
if (!response.body) return null;
|
|
7261
|
+
const reader = response.body.getReader();
|
|
7262
|
+
const chunks = [];
|
|
7263
|
+
let total = 0;
|
|
7264
|
+
try {
|
|
7265
|
+
for (; ; ) {
|
|
7266
|
+
const { done, value } = await race(reader.read());
|
|
7267
|
+
if (done) break;
|
|
7268
|
+
total += value.byteLength;
|
|
7269
|
+
if (total > maxBytes) throw tooLarge();
|
|
7270
|
+
chunks.push(value);
|
|
7271
|
+
}
|
|
7272
|
+
} catch (err) {
|
|
7273
|
+
reader.cancel().catch(() => {
|
|
7274
|
+
});
|
|
7275
|
+
throw err;
|
|
7276
|
+
}
|
|
7277
|
+
const out = new Uint8Array(new ArrayBuffer(total));
|
|
7278
|
+
let offset = 0;
|
|
7279
|
+
for (const chunk of chunks) {
|
|
7280
|
+
out.set(chunk, offset);
|
|
7281
|
+
offset += chunk.byteLength;
|
|
7282
|
+
}
|
|
7283
|
+
return out;
|
|
7284
|
+
}
|
|
7285
|
+
function createDefaultTrustFetcher() {
|
|
7286
|
+
return createGuardedFetcher({ allowHttp: true });
|
|
7287
|
+
}
|
|
7288
|
+
exports.GUARDED_FETCH_DEFAULTS = void 0; var REDIRECT_STATUSES, NULL_BODY_STATUSES, CROSS_ORIGIN_STRIPPED_HEADERS, BODY_HEADERS, dnsModule, defaultLookup;
|
|
7289
|
+
var init_guarded_fetch = __esm({
|
|
7290
|
+
"src/http/guarded-fetch.ts"() {
|
|
7291
|
+
init_reflect_polyfill();
|
|
7292
|
+
init_errors();
|
|
7293
|
+
init_ip_address();
|
|
7294
|
+
exports.GUARDED_FETCH_DEFAULTS = {
|
|
7295
|
+
maxRedirects: 3,
|
|
7296
|
+
timeoutMs: 15e3,
|
|
7297
|
+
maxResponseBytes: 16 * 1024 * 1024
|
|
7298
|
+
};
|
|
7299
|
+
REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
7300
|
+
NULL_BODY_STATUSES = /* @__PURE__ */ new Set([101, 103, 204, 205, 304]);
|
|
7301
|
+
CROSS_ORIGIN_STRIPPED_HEADERS = ["authorization", "cookie", "proxy-authorization"];
|
|
7302
|
+
BODY_HEADERS = ["content-type", "content-length", "content-encoding", "content-language", "content-location"];
|
|
7303
|
+
defaultLookup = async (hostname) => {
|
|
7304
|
+
const dns = await loadDns();
|
|
7305
|
+
if (!dns) return void 0;
|
|
7306
|
+
const records = await dns.lookup(hostname, { all: true, verbatim: true });
|
|
7307
|
+
return records.map((r) => r.address);
|
|
7308
|
+
};
|
|
7309
|
+
}
|
|
7310
|
+
});
|
|
7311
|
+
|
|
6844
7312
|
// src/trust/CrlFetcher.ts
|
|
6845
7313
|
function normalizeCrl(parsed, der) {
|
|
6846
7314
|
const thisUpdate = parsed.tbsCertList.thisUpdate.getTime();
|
|
@@ -6876,11 +7344,12 @@ var init_CrlFetcher = __esm({
|
|
|
6876
7344
|
init_es2015();
|
|
6877
7345
|
init_es20152();
|
|
6878
7346
|
init_ecdsa_util();
|
|
7347
|
+
init_guarded_fetch();
|
|
6879
7348
|
CrlFetcher = class {
|
|
6880
7349
|
fetcher;
|
|
6881
7350
|
cache;
|
|
6882
7351
|
constructor(opts = {}) {
|
|
6883
|
-
this.fetcher = opts.fetcher ??
|
|
7352
|
+
this.fetcher = opts.fetcher ?? createDefaultTrustFetcher();
|
|
6884
7353
|
this.cache = opts.cache;
|
|
6885
7354
|
}
|
|
6886
7355
|
async fetchAndParse(url) {
|
|
@@ -7612,12 +8081,13 @@ var init_OcspClient = __esm({
|
|
|
7612
8081
|
init_es20152();
|
|
7613
8082
|
init_ChainBuilder();
|
|
7614
8083
|
init_ecdsa_util();
|
|
8084
|
+
init_guarded_fetch();
|
|
7615
8085
|
DEFAULT_MAX_RESPONSE_AGE_MS = 7 * 24 * 3600 * 1e3;
|
|
7616
8086
|
OcspClient = class {
|
|
7617
8087
|
fetcher;
|
|
7618
8088
|
cache;
|
|
7619
8089
|
constructor(opts = {}) {
|
|
7620
|
-
this.fetcher = opts.fetcher ??
|
|
8090
|
+
this.fetcher = opts.fetcher ?? createDefaultTrustFetcher();
|
|
7621
8091
|
this.cache = opts.cache;
|
|
7622
8092
|
}
|
|
7623
8093
|
/**
|
|
@@ -8083,7 +8553,9 @@ var init_ProvenanceResolver = __esm({
|
|
|
8083
8553
|
// src/trust/TrustEvaluator.ts
|
|
8084
8554
|
var TrustEvaluator_exports = {};
|
|
8085
8555
|
__export(TrustEvaluator_exports, {
|
|
8086
|
-
|
|
8556
|
+
MAX_CREDENTIAL_CERTIFICATES: () => MAX_CREDENTIAL_CERTIFICATES,
|
|
8557
|
+
TrustEvaluator: () => TrustEvaluator,
|
|
8558
|
+
evaluateCredentialIssuer: () => evaluateCredentialIssuer
|
|
8087
8559
|
});
|
|
8088
8560
|
function deriveHint(leaf) {
|
|
8089
8561
|
const aki = leaf.getExtension(x509.AuthorityKeyIdentifierExtension);
|
|
@@ -8094,6 +8566,13 @@ function deriveHint(leaf) {
|
|
|
8094
8566
|
aki: akiBytes
|
|
8095
8567
|
};
|
|
8096
8568
|
}
|
|
8569
|
+
function deriveSelfHint(cert) {
|
|
8570
|
+
const skiHex = getSkiHex(cert);
|
|
8571
|
+
return {
|
|
8572
|
+
issuer: cert.subject,
|
|
8573
|
+
aki: skiHex ? hexToBytes(skiHex) : void 0
|
|
8574
|
+
};
|
|
8575
|
+
}
|
|
8097
8576
|
function hexToBytes(s) {
|
|
8098
8577
|
const clean = s.replace(/[^0-9a-f]/gi, "");
|
|
8099
8578
|
const out = new Uint8Array(clean.length / 2);
|
|
@@ -8125,7 +8604,31 @@ async function resolveProvenance(store, anchor) {
|
|
|
8125
8604
|
return void 0;
|
|
8126
8605
|
}
|
|
8127
8606
|
}
|
|
8128
|
-
|
|
8607
|
+
async function evaluateCredentialIssuer(input) {
|
|
8608
|
+
const [signerDer, ...rest] = input.chain;
|
|
8609
|
+
if (!signerDer) {
|
|
8610
|
+
throw new exports.MalformedCredentialError("Missing issuer certificate");
|
|
8611
|
+
}
|
|
8612
|
+
if (input.chain.length > MAX_CREDENTIAL_CERTIFICATES) {
|
|
8613
|
+
throw new exports.MalformedCredentialError(
|
|
8614
|
+
`x5c/x5chain carries ${input.chain.length} certificates; at most ${MAX_CREDENTIAL_CERTIFICATES} are accepted`
|
|
8615
|
+
);
|
|
8616
|
+
}
|
|
8617
|
+
const parse = (bytes, position) => {
|
|
8618
|
+
try {
|
|
8619
|
+
return new x509.X509Certificate(bytes);
|
|
8620
|
+
} catch (err) {
|
|
8621
|
+
throw new exports.MalformedCredentialError(
|
|
8622
|
+
`Invalid certificate at x5c/x5chain position ${position}: ${err.message}`
|
|
8623
|
+
);
|
|
8624
|
+
}
|
|
8625
|
+
};
|
|
8626
|
+
const leaf = parse(signerDer, 0);
|
|
8627
|
+
const intermediates = rest.map((bytes, i) => parse(bytes, i + 1));
|
|
8628
|
+
const { chain, issuedAt, ...evaluatorOptions } = input;
|
|
8629
|
+
return new TrustEvaluator(evaluatorOptions).evaluate(leaf, { intermediates, issuedAt });
|
|
8630
|
+
}
|
|
8631
|
+
var TrustEvaluator, MAX_CREDENTIAL_CERTIFICATES;
|
|
8129
8632
|
var init_TrustEvaluator = __esm({
|
|
8130
8633
|
"src/trust/TrustEvaluator.ts"() {
|
|
8131
8634
|
init_reflect_polyfill();
|
|
@@ -8151,13 +8654,10 @@ var init_TrustEvaluator = __esm({
|
|
|
8151
8654
|
revocationChecker;
|
|
8152
8655
|
async evaluate(leaf, context = {}) {
|
|
8153
8656
|
const hint = deriveHint(leaf);
|
|
8154
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8155
8657
|
const anchors = [];
|
|
8156
8658
|
const pushUnique = (batch) => {
|
|
8157
8659
|
for (const a of batch) {
|
|
8158
|
-
|
|
8159
|
-
if (seen.has(key)) continue;
|
|
8160
|
-
seen.add(key);
|
|
8660
|
+
if (anchors.some((x) => certificatesEqual(x.certificate, a.certificate))) continue;
|
|
8161
8661
|
anchors.push(a);
|
|
8162
8662
|
}
|
|
8163
8663
|
};
|
|
@@ -8165,6 +8665,7 @@ var init_TrustEvaluator = __esm({
|
|
|
8165
8665
|
for (const inter of context.intermediates ?? []) {
|
|
8166
8666
|
pushUnique(await this.trustStore.getAnchors(deriveHint(inter)));
|
|
8167
8667
|
}
|
|
8668
|
+
pushUnique(await this.trustStore.getAnchors(deriveSelfHint(leaf)));
|
|
8168
8669
|
if (anchors.length === 0) {
|
|
8169
8670
|
throw new exports.TrustAnchorNotFoundError(
|
|
8170
8671
|
`no trust anchor for issuer=${hint.issuer ?? "(none)"}`
|
|
@@ -8174,7 +8675,8 @@ var init_TrustEvaluator = __esm({
|
|
|
8174
8675
|
const chain = await this.chainBuilder.build(
|
|
8175
8676
|
leaf,
|
|
8176
8677
|
anchorCerts,
|
|
8177
|
-
context.intermediates ?? []
|
|
8678
|
+
context.intermediates ?? [],
|
|
8679
|
+
{ issuedAt: context.issuedAt }
|
|
8178
8680
|
);
|
|
8179
8681
|
const terminusCert = chain[chain.length - 1];
|
|
8180
8682
|
const anchor = anchors.find(
|
|
@@ -8214,6 +8716,7 @@ var init_TrustEvaluator = __esm({
|
|
|
8214
8716
|
};
|
|
8215
8717
|
}
|
|
8216
8718
|
};
|
|
8719
|
+
MAX_CREDENTIAL_CERTIFICATES = 10;
|
|
8217
8720
|
}
|
|
8218
8721
|
});
|
|
8219
8722
|
|
|
@@ -9005,6 +9508,14 @@ function bytesEqual3(a, b) {
|
|
|
9005
9508
|
}
|
|
9006
9509
|
return true;
|
|
9007
9510
|
}
|
|
9511
|
+
function peekMsoSigned(payload) {
|
|
9512
|
+
try {
|
|
9513
|
+
const signed = decodeMso(payload).validityInfo.signed;
|
|
9514
|
+
return Number.isFinite(signed.getTime()) ? signed : void 0;
|
|
9515
|
+
} catch {
|
|
9516
|
+
return void 0;
|
|
9517
|
+
}
|
|
9518
|
+
}
|
|
9008
9519
|
function derToPem(der) {
|
|
9009
9520
|
let binary = "";
|
|
9010
9521
|
for (const b of der) binary += String.fromCharCode(b);
|
|
@@ -9165,17 +9676,17 @@ var MdocParser = class {
|
|
|
9165
9676
|
}
|
|
9166
9677
|
let trustResult;
|
|
9167
9678
|
if (options.trustStore) {
|
|
9168
|
-
const {
|
|
9169
|
-
|
|
9679
|
+
const { evaluateCredentialIssuer: evaluateCredentialIssuer2 } = await Promise.resolve().then(() => (init_TrustEvaluator(), TrustEvaluator_exports));
|
|
9680
|
+
trustResult = await evaluateCredentialIssuer2({
|
|
9681
|
+
chain: cose.x5chain,
|
|
9682
|
+
profile: "iso18013-5",
|
|
9683
|
+
issuedAt: peekMsoSigned(cose.payload),
|
|
9170
9684
|
trustStore: options.trustStore,
|
|
9171
9685
|
revocationPolicy: options.revocationPolicy ?? "skip",
|
|
9172
9686
|
fetcher: options.fetcher,
|
|
9173
9687
|
cache: options.cache,
|
|
9174
9688
|
clockSkewTolerance: options.clockSkewTolerance
|
|
9175
9689
|
});
|
|
9176
|
-
const { X509Certificate: X509Certificate9 } = await import('@peculiar/x509');
|
|
9177
|
-
const leaf = new X509Certificate9(issuerCertBytes);
|
|
9178
|
-
trustResult = await evaluator.evaluate(leaf);
|
|
9179
9690
|
} else {
|
|
9180
9691
|
if (options.trustedCertificates.length === 0) {
|
|
9181
9692
|
if (options.skipTrustCheck !== true) {
|
|
@@ -9324,6 +9835,13 @@ var MdocParser = class {
|
|
|
9324
9835
|
init_reflect_polyfill();
|
|
9325
9836
|
init_errors();
|
|
9326
9837
|
var DEFAULT_ALLOWED_ALGORITHMS2 = ["ES256", "ES384", "ES512"];
|
|
9838
|
+
var SD_JWT_VC_TYP = "dc+sd-jwt";
|
|
9839
|
+
var LEGACY_SD_JWT_VC_TYP = "vc+sd-jwt";
|
|
9840
|
+
function normalizeTyp(typ) {
|
|
9841
|
+
if (typeof typ !== "string") return void 0;
|
|
9842
|
+
const lower = typ.toLowerCase();
|
|
9843
|
+
return lower.startsWith("application/") ? lower.slice("application/".length) : lower;
|
|
9844
|
+
}
|
|
9327
9845
|
var sha256Hasher = async (data, _alg) => {
|
|
9328
9846
|
const encoder2 = new TextEncoder();
|
|
9329
9847
|
const input = typeof data === "string" ? encoder2.encode(data) : data;
|
|
@@ -9436,6 +9954,18 @@ var SdJwtParser = class {
|
|
|
9436
9954
|
if (typeof alg !== "string" || !allowedAlgorithms.includes(alg)) {
|
|
9437
9955
|
return invalidResult(`Unsupported algorithm: ${alg}`);
|
|
9438
9956
|
}
|
|
9957
|
+
const typ = normalizeTyp(header.typ);
|
|
9958
|
+
if (typ === LEGACY_SD_JWT_VC_TYP) {
|
|
9959
|
+
if (options.allowLegacyVcSdJwtTyp !== true) {
|
|
9960
|
+
return invalidResult(
|
|
9961
|
+
`Legacy SD-JWT VC typ '${LEGACY_SD_JWT_VC_TYP}' is not accepted (removed in draft-ietf-oauth-sd-jwt-vc-19; expected '${SD_JWT_VC_TYP}'). Set allowLegacyVcSdJwtTyp to accept it during migration.`
|
|
9962
|
+
);
|
|
9963
|
+
}
|
|
9964
|
+
} else if (typ !== SD_JWT_VC_TYP) {
|
|
9965
|
+
return invalidResult(
|
|
9966
|
+
`Unsupported SD-JWT VC typ: ${header.typ === void 0 ? "(missing)" : String(header.typ)} (expected '${SD_JWT_VC_TYP}')`
|
|
9967
|
+
);
|
|
9968
|
+
}
|
|
9439
9969
|
const x5cArray = header.x5c;
|
|
9440
9970
|
const hasX5c = Array.isArray(x5cArray) && x5cArray.length > 0 && typeof x5cArray[0] === "string";
|
|
9441
9971
|
let issuerKey;
|
|
@@ -9505,17 +10035,22 @@ var SdJwtParser = class {
|
|
|
9505
10035
|
};
|
|
9506
10036
|
let trustResult;
|
|
9507
10037
|
if (issuerCertBytes === void 0) ; else if (options.trustStore) {
|
|
9508
|
-
const {
|
|
9509
|
-
const
|
|
10038
|
+
const { evaluateCredentialIssuer: evaluateCredentialIssuer2 } = await Promise.resolve().then(() => (init_TrustEvaluator(), TrustEvaluator_exports));
|
|
10039
|
+
const x5cRest = x5cArray.slice(1);
|
|
10040
|
+
if (!x5cRest.every((entry) => typeof entry === "string")) {
|
|
10041
|
+
throw new exports.MalformedCredentialError("x5c entries must be base64 strings");
|
|
10042
|
+
}
|
|
10043
|
+
const iat = payload["iat"];
|
|
10044
|
+
trustResult = await evaluateCredentialIssuer2({
|
|
10045
|
+
chain: [issuerCertBytes, ...x5cRest.map((entry) => base64ToBytes(entry))],
|
|
10046
|
+
profile: "generic",
|
|
10047
|
+
issuedAt: typeof iat === "number" && Number.isFinite(iat) ? new Date(iat * 1e3) : void 0,
|
|
9510
10048
|
trustStore: options.trustStore,
|
|
9511
10049
|
revocationPolicy: options.revocationPolicy ?? "skip",
|
|
9512
10050
|
fetcher: options.fetcher,
|
|
9513
10051
|
cache: options.cache,
|
|
9514
10052
|
clockSkewTolerance: options.clockSkewTolerance
|
|
9515
10053
|
});
|
|
9516
|
-
const { X509Certificate: X509Certificate9 } = await import('@peculiar/x509');
|
|
9517
|
-
const leaf = new X509Certificate9(issuerCertBytes);
|
|
9518
|
-
trustResult = await evaluator.evaluate(leaf);
|
|
9519
10054
|
} else {
|
|
9520
10055
|
if (options.trustedCertificates.length === 0) {
|
|
9521
10056
|
if (options.skipTrustCheck !== true) {
|
|
@@ -9951,6 +10486,9 @@ var InMemoryCache = class {
|
|
|
9951
10486
|
}
|
|
9952
10487
|
};
|
|
9953
10488
|
|
|
10489
|
+
// src/index.ts
|
|
10490
|
+
init_guarded_fetch();
|
|
10491
|
+
|
|
9954
10492
|
// src/trust/index.ts
|
|
9955
10493
|
init_reflect_polyfill();
|
|
9956
10494
|
|
|
@@ -9998,14 +10536,10 @@ var CompositeTrustStore = class {
|
|
|
9998
10536
|
const results = await Promise.all(
|
|
9999
10537
|
this.stores.map((store) => store.getAnchors(hint))
|
|
10000
10538
|
);
|
|
10001
|
-
const seen = /* @__PURE__ */ new Set();
|
|
10002
10539
|
const out = [];
|
|
10003
10540
|
for (const batch of results) {
|
|
10004
10541
|
for (const anchor of batch) {
|
|
10005
|
-
|
|
10006
|
-
const key = ski ?? anchor.certificate.serialNumber;
|
|
10007
|
-
if (seen.has(key)) continue;
|
|
10008
|
-
seen.add(key);
|
|
10542
|
+
if (out.some((a) => certificatesEqual(a.certificate, anchor.certificate))) continue;
|
|
10009
10543
|
out.push(anchor);
|
|
10010
10544
|
}
|
|
10011
10545
|
}
|
|
@@ -10099,11 +10633,12 @@ var EU_LOTL_SIGNING_ANCHORS = Object.freeze(
|
|
|
10099
10633
|
// src/trust/LotlFetcher.ts
|
|
10100
10634
|
init_reflect_polyfill();
|
|
10101
10635
|
init_errors();
|
|
10636
|
+
init_guarded_fetch();
|
|
10102
10637
|
var XMLDSIG_NS = "http://www.w3.org/2000/09/xmldsig#";
|
|
10103
10638
|
var LotlFetcher = class {
|
|
10104
10639
|
fetcher;
|
|
10105
10640
|
constructor(opts = {}) {
|
|
10106
|
-
this.fetcher = opts.fetcher ??
|
|
10641
|
+
this.fetcher = opts.fetcher ?? createDefaultTrustFetcher();
|
|
10107
10642
|
}
|
|
10108
10643
|
async fetchSigned(url, signingAnchors) {
|
|
10109
10644
|
assertCryptoEngineRegistered();
|
|
@@ -10565,7 +11100,7 @@ function equalBytes(a, b) {
|
|
|
10565
11100
|
|
|
10566
11101
|
// src/index.ts
|
|
10567
11102
|
init_errors();
|
|
10568
|
-
var VERSION = "0.
|
|
11103
|
+
var VERSION = "0.13.0";
|
|
10569
11104
|
/*! Bundled license information:
|
|
10570
11105
|
|
|
10571
11106
|
pvtsutils/build/index.es.js:
|
|
@@ -10656,6 +11191,7 @@ exports.buildHaipQuery = buildHaipQuery;
|
|
|
10656
11191
|
exports.buildOid4vpSessionTranscript = buildOid4vpSessionTranscript;
|
|
10657
11192
|
exports.buildOpenID4VPHandoverSessionTranscript = buildOpenID4VPHandoverSessionTranscript;
|
|
10658
11193
|
exports.createAuthorizationRequest = createAuthorizationRequest;
|
|
11194
|
+
exports.createGuardedFetcher = createGuardedFetcher;
|
|
10659
11195
|
exports.createSignedAuthorizationRequest = createSignedAuthorizationRequest;
|
|
10660
11196
|
exports.decryptAuthorizationResponse = decryptAuthorizationResponse;
|
|
10661
11197
|
exports.isHaipQuery = isHaipQuery;
|