@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.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
-
import { X509Certificate, SubjectKeyIdentifierExtension,
|
|
2
|
+
import { X509Certificate, SubjectKeyIdentifierExtension, BasicConstraintsExtension, KeyUsagesExtension, KeyUsageFlags as KeyUsageFlags$1, ExtendedKeyUsageExtension, AuthorityKeyIdentifierExtension, SubjectAlternativeNameExtension, Name as Name$1 } from '@peculiar/x509';
|
|
3
3
|
import { v4 } from 'uuid';
|
|
4
4
|
import { SignJWT, importX509, decodeProtectedHeader, jwtVerify, importJWK, compactDecrypt } from 'jose';
|
|
5
5
|
import { Encoder, Tag } from 'cbor-x';
|
|
@@ -24,7 +24,7 @@ var init_reflect_polyfill = __esm({
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
// src/errors.ts
|
|
27
|
-
var InvalidSignatureError, ExpiredCredentialError, UnsupportedFormatError, MalformedCredentialError, NonceValidationError, HaipValidationError, OpenID4VPError, TrustAnchorNotFoundError, CertificateChainError, RevokedCertificateError, RevocationCheckFailedError, LotlFetchError, LotlSignatureError, LotlConfigurationError, SignedRequestBuildError, UnsupportedJweError, DecryptionFailedError, MissingDecryptionKeyError, MissingVerifierEncryptionKeyError, MultipleCredentialsNotSupportedError;
|
|
27
|
+
var InvalidSignatureError, ExpiredCredentialError, UnsupportedFormatError, MalformedCredentialError, NonceValidationError, HaipValidationError, OpenID4VPError, TrustAnchorNotFoundError, CertificateChainError, RevokedCertificateError, RevocationCheckFailedError, LotlFetchError, LotlSignatureError, LotlConfigurationError, SignedRequestBuildError, UnsupportedJweError, DecryptionFailedError, MissingDecryptionKeyError, MissingVerifierEncryptionKeyError, MultipleCredentialsNotSupportedError, GuardedFetchError;
|
|
28
28
|
var init_errors = __esm({
|
|
29
29
|
"src/errors.ts"() {
|
|
30
30
|
init_reflect_polyfill();
|
|
@@ -171,6 +171,16 @@ var init_errors = __esm({
|
|
|
171
171
|
this.presentationCount = presentationCount;
|
|
172
172
|
}
|
|
173
173
|
};
|
|
174
|
+
GuardedFetchError = class extends OpenID4VPError {
|
|
175
|
+
code = "guarded_fetch_rejected";
|
|
176
|
+
reason;
|
|
177
|
+
url;
|
|
178
|
+
constructor(reason, message, options) {
|
|
179
|
+
super(message, { cause: options.cause });
|
|
180
|
+
this.reason = reason;
|
|
181
|
+
this.url = options.url;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
174
184
|
}
|
|
175
185
|
});
|
|
176
186
|
|
|
@@ -6567,7 +6577,7 @@ function dnIsUnder(childDn, subtreeDn) {
|
|
|
6567
6577
|
}
|
|
6568
6578
|
return true;
|
|
6569
6579
|
}
|
|
6570
|
-
var NAME_CONSTRAINTS_OID, DEFAULT_ALGORITHMS, ChainBuilder;
|
|
6580
|
+
var NAME_CONSTRAINTS_OID, ISO_18013_5_DS_EKU, DEFAULT_MAX_CHAIN_LENGTH, DEFAULT_MAX_SIGNATURE_CHECKS, searchAborts, DEFAULT_ALGORITHMS, ChainBuilder;
|
|
6571
6581
|
var init_ChainBuilder = __esm({
|
|
6572
6582
|
"src/trust/ChainBuilder.ts"() {
|
|
6573
6583
|
init_reflect_polyfill();
|
|
@@ -6576,6 +6586,10 @@ var init_ChainBuilder = __esm({
|
|
|
6576
6586
|
init_errors();
|
|
6577
6587
|
init_x509_utils();
|
|
6578
6588
|
NAME_CONSTRAINTS_OID = "2.5.29.30";
|
|
6589
|
+
ISO_18013_5_DS_EKU = "1.0.18013.5.1.2";
|
|
6590
|
+
DEFAULT_MAX_CHAIN_LENGTH = 5;
|
|
6591
|
+
DEFAULT_MAX_SIGNATURE_CHECKS = 32;
|
|
6592
|
+
searchAborts = /* @__PURE__ */ new WeakSet();
|
|
6579
6593
|
DEFAULT_ALGORITHMS = [
|
|
6580
6594
|
"ES256",
|
|
6581
6595
|
"ES384",
|
|
@@ -6598,62 +6612,130 @@ var init_ChainBuilder = __esm({
|
|
|
6598
6612
|
* optionally using additional intermediates. Returns the ordered chain
|
|
6599
6613
|
* (leaf → anchor). Throws `CertificateChainError` on any validation
|
|
6600
6614
|
* failure.
|
|
6615
|
+
*
|
|
6616
|
+
* Trust is by presence in the anchor set and is established ONLY by (a) the
|
|
6617
|
+
* leaf being byte-identical to an anchor, or (b) a verified signature by an
|
|
6618
|
+
* anchor's key on the last certificate of the path. Subject/Issuer DN
|
|
6619
|
+
* equality is used to select candidates, never to conclude trust
|
|
6620
|
+
* (GHSA-4c2f-96cf-f5fc).
|
|
6621
|
+
*
|
|
6622
|
+
* The search is depth-first and backtracks: when several intermediates share
|
|
6623
|
+
* a Subject DN — a re-issued certificate next to its expired predecessor, or
|
|
6624
|
+
* cross-signed copies under different roots — a candidate that fails a check
|
|
6625
|
+
* or leads nowhere does not end the search. The result therefore does not
|
|
6626
|
+
* depend on the order of `intermediates`. Work is bounded by
|
|
6627
|
+
* `maxChainLength` and `maxSignatureChecks`.
|
|
6601
6628
|
*/
|
|
6602
|
-
async build(leaf, anchors, intermediates = []) {
|
|
6603
|
-
const
|
|
6629
|
+
async build(leaf, anchors, intermediates = [], context = {}) {
|
|
6630
|
+
const search = {
|
|
6631
|
+
remaining: this.opts.maxSignatureChecks ?? DEFAULT_MAX_SIGNATURE_CHECKS
|
|
6632
|
+
};
|
|
6633
|
+
let lastFailure;
|
|
6604
6634
|
for (const anchor of anchors) {
|
|
6635
|
+
search.specific = void 0;
|
|
6636
|
+
search.fallback = void 0;
|
|
6605
6637
|
try {
|
|
6606
|
-
|
|
6638
|
+
const chain = await this.tryBuild(leaf, anchor, intermediates, context, search);
|
|
6639
|
+
if (chain) return chain;
|
|
6640
|
+
lastFailure = search.specific ?? search.fallback;
|
|
6607
6641
|
} catch (err) {
|
|
6608
|
-
|
|
6642
|
+
if (searchAborts.has(err)) throw err;
|
|
6643
|
+
lastFailure = err;
|
|
6609
6644
|
}
|
|
6610
6645
|
}
|
|
6611
|
-
|
|
6612
|
-
if (last instanceof CertificateChainError) throw last;
|
|
6646
|
+
if (lastFailure instanceof CertificateChainError) throw lastFailure;
|
|
6613
6647
|
throw new CertificateChainError("no valid chain to any anchor", {
|
|
6614
6648
|
reason: "signature",
|
|
6615
|
-
cause:
|
|
6649
|
+
cause: lastFailure
|
|
6616
6650
|
});
|
|
6617
6651
|
}
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
this.
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6652
|
+
/** Search for a path from `leaf` that closes at `anchor`. */
|
|
6653
|
+
async tryBuild(leaf, anchor, intermediates, context, search) {
|
|
6654
|
+
this.checkPerCert(leaf, context);
|
|
6655
|
+
if (certificatesEqual(leaf, anchor)) {
|
|
6656
|
+
this.checkLeafKeyUsage(leaf, "generic");
|
|
6657
|
+
return [leaf];
|
|
6658
|
+
}
|
|
6659
|
+
const profile = this.opts.profile ?? "generic";
|
|
6660
|
+
this.checkLeafKeyUsage(leaf, profile);
|
|
6661
|
+
this.checkLeafExtendedKeyUsage(leaf, profile);
|
|
6662
|
+
const pool = intermediates.filter(
|
|
6663
|
+
(c) => !certificatesEqual(c, anchor) && !certificatesEqual(c, leaf)
|
|
6664
|
+
);
|
|
6665
|
+
return this.extendPath([leaf], anchor, pool, context, search);
|
|
6666
|
+
}
|
|
6667
|
+
/**
|
|
6668
|
+
* Try to complete `chain` (leaf first) to `anchor`. Returns the completed
|
|
6669
|
+
* chain, or `undefined` when no extension of this prefix works — failures
|
|
6670
|
+
* are recorded on `search` so the caller can report the most specific one.
|
|
6671
|
+
* Throws only to abandon the whole search (signature budget exhausted).
|
|
6672
|
+
*/
|
|
6673
|
+
async extendPath(chain, anchor, pool, context, search) {
|
|
6674
|
+
const leaf = chain[0];
|
|
6675
|
+
const current = chain[chain.length - 1];
|
|
6676
|
+
const maxLength = this.opts.maxChainLength ?? DEFAULT_MAX_CHAIN_LENGTH;
|
|
6677
|
+
if (chain.length + 1 > maxLength) {
|
|
6678
|
+
search.specific = new CertificateChainError(
|
|
6679
|
+
`chain from ${leaf.subject} exceeds the maximum length of ${maxLength} certificates`,
|
|
6680
|
+
{ reason: "path_length" }
|
|
6681
|
+
);
|
|
6682
|
+
return void 0;
|
|
6683
|
+
}
|
|
6684
|
+
const nonLeafDepth = chain.length - 1;
|
|
6685
|
+
if (current.issuer === anchor.subject) {
|
|
6686
|
+
try {
|
|
6687
|
+
await this.closeAtAnchor(current, anchor, nonLeafDepth, context, search);
|
|
6688
|
+
const closed = [...chain, anchor];
|
|
6689
|
+
this.checkNameConstraints(leaf, closed.slice(1));
|
|
6690
|
+
return closed;
|
|
6691
|
+
} catch (err) {
|
|
6692
|
+
if (searchAborts.has(err)) throw err;
|
|
6693
|
+
search.specific = err;
|
|
6640
6694
|
}
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6695
|
+
}
|
|
6696
|
+
const candidates = pool.filter(
|
|
6697
|
+
(c) => c.subject === current.issuer && !chain.some((x) => certificatesEqual(x, c))
|
|
6698
|
+
);
|
|
6699
|
+
let verified = 0;
|
|
6700
|
+
for (const candidate of candidates) {
|
|
6701
|
+
if (!await this.signatureVerifies(current, candidate, search)) continue;
|
|
6702
|
+
verified++;
|
|
6703
|
+
try {
|
|
6704
|
+
this.checkPerCert(candidate, context);
|
|
6705
|
+
this.checkCaAndPathLen(candidate, nonLeafDepth);
|
|
6706
|
+
this.checkAkiSkiMatch(current, candidate);
|
|
6707
|
+
} catch (err) {
|
|
6708
|
+
search.specific = err;
|
|
6709
|
+
continue;
|
|
6710
|
+
}
|
|
6711
|
+
const found = await this.extendPath([...chain, candidate], anchor, pool, context, search);
|
|
6712
|
+
if (found) return found;
|
|
6713
|
+
}
|
|
6714
|
+
search.fallback = new CertificateChainError(
|
|
6715
|
+
candidates.length > 0 && verified === 0 ? `signature verification failed for ${current.subject}` : `no issuer certificate found for ${current.subject}`,
|
|
6716
|
+
{ reason: "signature" }
|
|
6717
|
+
);
|
|
6718
|
+
return void 0;
|
|
6719
|
+
}
|
|
6720
|
+
async closeAtAnchor(current, anchor, nonLeafDepth, context, search) {
|
|
6721
|
+
this.checkPerCert(anchor, context);
|
|
6722
|
+
this.checkCaAndPathLen(anchor, nonLeafDepth);
|
|
6723
|
+
this.checkAkiSkiMatch(current, anchor);
|
|
6724
|
+
this.spendSignatureCheck(search);
|
|
6725
|
+
await this.verifySignature(current, anchor);
|
|
6726
|
+
}
|
|
6727
|
+
/** Charge one signature verification to the search, or abandon it. */
|
|
6728
|
+
spendSignatureCheck(search) {
|
|
6729
|
+
if (search.remaining <= 0) {
|
|
6730
|
+
const limit = this.opts.maxSignatureChecks ?? DEFAULT_MAX_SIGNATURE_CHECKS;
|
|
6731
|
+
const err = new CertificateChainError(
|
|
6732
|
+
`certificate path search exceeded ${limit} signature checks`,
|
|
6733
|
+
{ reason: "path_length" }
|
|
6653
6734
|
);
|
|
6735
|
+
searchAborts.add(err);
|
|
6736
|
+
throw err;
|
|
6654
6737
|
}
|
|
6655
|
-
|
|
6656
|
-
return chain;
|
|
6738
|
+
search.remaining--;
|
|
6657
6739
|
}
|
|
6658
6740
|
/**
|
|
6659
6741
|
* Enforce nameConstraints asserted by any CA above the leaf against the
|
|
@@ -6702,7 +6784,7 @@ var init_ChainBuilder = __esm({
|
|
|
6702
6784
|
}
|
|
6703
6785
|
}
|
|
6704
6786
|
checkCaAndPathLen(cert, nonLeafDepth) {
|
|
6705
|
-
this.checkCaKeyUsage(cert);
|
|
6787
|
+
this.checkCaKeyUsage(cert, this.opts.profile ?? "generic");
|
|
6706
6788
|
const bc = cert.getExtension(BasicConstraintsExtension);
|
|
6707
6789
|
if (!bc || !bc.ca) {
|
|
6708
6790
|
throw new CertificateChainError(
|
|
@@ -6717,9 +6799,17 @@ var init_ChainBuilder = __esm({
|
|
|
6717
6799
|
);
|
|
6718
6800
|
}
|
|
6719
6801
|
}
|
|
6720
|
-
checkLeafKeyUsage(leaf) {
|
|
6802
|
+
checkLeafKeyUsage(leaf, profile) {
|
|
6721
6803
|
const ext = leaf.getExtension(KeyUsagesExtension);
|
|
6722
|
-
if (!ext)
|
|
6804
|
+
if (!ext) {
|
|
6805
|
+
if (profile === "iso18013-5") {
|
|
6806
|
+
throw new CertificateChainError(
|
|
6807
|
+
`document signer ${leaf.subject} has no keyUsage extension (ISO 18013-5 requires digitalSignature)`,
|
|
6808
|
+
{ reason: "key_usage" }
|
|
6809
|
+
);
|
|
6810
|
+
}
|
|
6811
|
+
return;
|
|
6812
|
+
}
|
|
6723
6813
|
if ((ext.usages & KeyUsageFlags$1.digitalSignature) === 0) {
|
|
6724
6814
|
throw new CertificateChainError(
|
|
6725
6815
|
`leaf ${leaf.subject} does not assert digitalSignature key usage`,
|
|
@@ -6727,9 +6817,28 @@ var init_ChainBuilder = __esm({
|
|
|
6727
6817
|
);
|
|
6728
6818
|
}
|
|
6729
6819
|
}
|
|
6730
|
-
|
|
6820
|
+
checkLeafExtendedKeyUsage(leaf, profile) {
|
|
6821
|
+
if (profile !== "iso18013-5") return;
|
|
6822
|
+
const ext = leaf.getExtension(ExtendedKeyUsageExtension);
|
|
6823
|
+
const usages = (ext?.usages ?? []).map(String);
|
|
6824
|
+
if (!usages.includes(ISO_18013_5_DS_EKU)) {
|
|
6825
|
+
throw new CertificateChainError(
|
|
6826
|
+
`document signer ${leaf.subject} lacks extended key usage ${ISO_18013_5_DS_EKU} (id-mdl-kp-mdlDS)`,
|
|
6827
|
+
{ reason: "extended_key_usage" }
|
|
6828
|
+
);
|
|
6829
|
+
}
|
|
6830
|
+
}
|
|
6831
|
+
checkCaKeyUsage(cert, profile) {
|
|
6731
6832
|
const ext = cert.getExtension(KeyUsagesExtension);
|
|
6732
|
-
if (!ext)
|
|
6833
|
+
if (!ext) {
|
|
6834
|
+
if (profile === "iso18013-5") {
|
|
6835
|
+
throw new CertificateChainError(
|
|
6836
|
+
`CA ${cert.subject} has no keyUsage extension (ISO 18013-5 requires keyCertSign)`,
|
|
6837
|
+
{ reason: "key_usage" }
|
|
6838
|
+
);
|
|
6839
|
+
}
|
|
6840
|
+
return;
|
|
6841
|
+
}
|
|
6733
6842
|
if ((ext.usages & KeyUsageFlags$1.keyCertSign) === 0) {
|
|
6734
6843
|
throw new CertificateChainError(
|
|
6735
6844
|
`CA ${cert.subject} does not assert keyCertSign key usage`,
|
|
@@ -6750,30 +6859,46 @@ var init_ChainBuilder = __esm({
|
|
|
6750
6859
|
);
|
|
6751
6860
|
}
|
|
6752
6861
|
}
|
|
6862
|
+
/**
|
|
6863
|
+
* Signature check used to select among DN-matching candidates. Never throws
|
|
6864
|
+
* on a bad signature; throws only when the search budget is exhausted.
|
|
6865
|
+
*/
|
|
6866
|
+
async signatureVerifies(child, issuer, search) {
|
|
6867
|
+
this.spendSignatureCheck(search);
|
|
6868
|
+
try {
|
|
6869
|
+
return await child.verify({ publicKey: issuer.publicKey, signatureOnly: true });
|
|
6870
|
+
} catch {
|
|
6871
|
+
return false;
|
|
6872
|
+
}
|
|
6873
|
+
}
|
|
6753
6874
|
async verifySignature(child, issuer) {
|
|
6754
6875
|
const ok = await child.verify({ publicKey: issuer.publicKey, signatureOnly: true });
|
|
6755
6876
|
if (!ok) {
|
|
6756
6877
|
throw new CertificateChainError(`signature verification failed for ${child.subject}`, { reason: "signature" });
|
|
6757
6878
|
}
|
|
6758
6879
|
}
|
|
6759
|
-
checkPerCert(cert) {
|
|
6760
|
-
this.
|
|
6880
|
+
checkPerCert(cert, context = {}) {
|
|
6881
|
+
const now = (this.opts.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
6882
|
+
this.checkValidity(cert, now, "");
|
|
6883
|
+
if (context.issuedAt) {
|
|
6884
|
+
this.checkValidity(cert, context.issuedAt, ` at issuance time ${context.issuedAt.toISOString()}`);
|
|
6885
|
+
}
|
|
6761
6886
|
this.checkAlgorithm(cert);
|
|
6762
6887
|
}
|
|
6763
|
-
checkValidity(cert) {
|
|
6764
|
-
const
|
|
6888
|
+
checkValidity(cert, at, label) {
|
|
6889
|
+
const time = at.getTime();
|
|
6765
6890
|
const skewMs = (this.opts.clockSkewTolerance ?? 60) * 1e3;
|
|
6766
6891
|
const notBefore = cert.notBefore.getTime();
|
|
6767
6892
|
const notAfter = cert.notAfter.getTime();
|
|
6768
|
-
if (
|
|
6893
|
+
if (time + skewMs < notBefore) {
|
|
6769
6894
|
throw new CertificateChainError(
|
|
6770
|
-
`certificate ${cert.subject} not yet valid (notBefore=${cert.notBefore.toISOString()})`,
|
|
6895
|
+
`certificate ${cert.subject} not yet valid${label} (notBefore=${cert.notBefore.toISOString()})`,
|
|
6771
6896
|
{ reason: "validity" }
|
|
6772
6897
|
);
|
|
6773
6898
|
}
|
|
6774
|
-
if (
|
|
6899
|
+
if (time - skewMs > notAfter) {
|
|
6775
6900
|
throw new CertificateChainError(
|
|
6776
|
-
`certificate ${cert.subject} expired (notAfter=${cert.notAfter.toISOString()})`,
|
|
6901
|
+
`certificate ${cert.subject} expired${label} (notAfter=${cert.notAfter.toISOString()})`,
|
|
6777
6902
|
{ reason: "validity" }
|
|
6778
6903
|
);
|
|
6779
6904
|
}
|
|
@@ -6820,6 +6945,349 @@ var init_ecdsa_util = __esm({
|
|
|
6820
6945
|
}
|
|
6821
6946
|
});
|
|
6822
6947
|
|
|
6948
|
+
// src/http/ip-address.ts
|
|
6949
|
+
function parseIPv4(input) {
|
|
6950
|
+
const parts = input.split(".");
|
|
6951
|
+
if (parts.length !== 4) return void 0;
|
|
6952
|
+
const bytes = [];
|
|
6953
|
+
for (const part of parts) {
|
|
6954
|
+
if (!/^\d{1,3}$/.test(part)) return void 0;
|
|
6955
|
+
const value = Number(part);
|
|
6956
|
+
if (value > 255) return void 0;
|
|
6957
|
+
bytes.push(value);
|
|
6958
|
+
}
|
|
6959
|
+
return bytes;
|
|
6960
|
+
}
|
|
6961
|
+
function parseIPv6(input) {
|
|
6962
|
+
const address = input.split("%")[0];
|
|
6963
|
+
if (!address.includes(":")) return void 0;
|
|
6964
|
+
let head = address;
|
|
6965
|
+
let embeddedV4;
|
|
6966
|
+
const lastColon = address.lastIndexOf(":");
|
|
6967
|
+
const tail = address.slice(lastColon + 1);
|
|
6968
|
+
if (tail.includes(".")) {
|
|
6969
|
+
embeddedV4 = parseIPv4(tail);
|
|
6970
|
+
if (!embeddedV4) return void 0;
|
|
6971
|
+
head = address.slice(0, lastColon + 1) + "0:0";
|
|
6972
|
+
}
|
|
6973
|
+
const doubleColon = head.split("::");
|
|
6974
|
+
if (doubleColon.length > 2) return void 0;
|
|
6975
|
+
const toGroups = (s) => s === "" ? [] : s.split(":");
|
|
6976
|
+
const left = toGroups(doubleColon[0]);
|
|
6977
|
+
const right = doubleColon.length === 2 ? toGroups(doubleColon[1]) : [];
|
|
6978
|
+
const missing = 8 - left.length - right.length;
|
|
6979
|
+
if (doubleColon.length === 2 ? missing < 1 : missing !== 0) return void 0;
|
|
6980
|
+
const groups = [...left, ...Array(doubleColon.length === 2 ? missing : 0).fill("0"), ...right];
|
|
6981
|
+
const bytes = [];
|
|
6982
|
+
for (const group of groups) {
|
|
6983
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(group)) return void 0;
|
|
6984
|
+
const value = parseInt(group, 16);
|
|
6985
|
+
bytes.push(value >> 8, value & 255);
|
|
6986
|
+
}
|
|
6987
|
+
if (embeddedV4) bytes.splice(12, 4, ...embeddedV4);
|
|
6988
|
+
return bytes;
|
|
6989
|
+
}
|
|
6990
|
+
function inPrefix(bytes, prefix, bits) {
|
|
6991
|
+
for (let i = 0; i < bits; i++) {
|
|
6992
|
+
const byte = i >> 3;
|
|
6993
|
+
const mask = 128 >> (i & 7);
|
|
6994
|
+
if ((bytes[byte] & mask) !== ((prefix[byte] ?? 0) & mask)) return false;
|
|
6995
|
+
}
|
|
6996
|
+
return true;
|
|
6997
|
+
}
|
|
6998
|
+
function isPublicV4(bytes) {
|
|
6999
|
+
return !BLOCKED_V4.some(([prefix, bits]) => inPrefix(bytes, prefix, bits));
|
|
7000
|
+
}
|
|
7001
|
+
function isPublicV6(bytes) {
|
|
7002
|
+
const first10Zero = bytes.slice(0, 10).every((b) => b === 0);
|
|
7003
|
+
if (first10Zero && bytes[10] === 0 && bytes[11] === 0) {
|
|
7004
|
+
const v4 = bytes.slice(12);
|
|
7005
|
+
if (v4.slice(0, 3).every((b) => b === 0)) return false;
|
|
7006
|
+
return isPublicV4(v4);
|
|
7007
|
+
}
|
|
7008
|
+
if (first10Zero && bytes[10] === 255 && bytes[11] === 255) return isPublicV4(bytes.slice(12));
|
|
7009
|
+
if (inPrefix(bytes, [0, 100, 255, 155], 96)) return isPublicV4(bytes.slice(12));
|
|
7010
|
+
if (inPrefix(bytes, [32, 2], 16)) return isPublicV4(bytes.slice(2, 6));
|
|
7011
|
+
return !BLOCKED_V6.some(([prefix, bits]) => inPrefix(bytes, prefix, bits));
|
|
7012
|
+
}
|
|
7013
|
+
function isPublicIpAddress(ip) {
|
|
7014
|
+
const v4 = parseIPv4(ip);
|
|
7015
|
+
if (v4) return isPublicV4(v4);
|
|
7016
|
+
const v6 = parseIPv6(ip);
|
|
7017
|
+
if (v6) return isPublicV6(v6);
|
|
7018
|
+
return false;
|
|
7019
|
+
}
|
|
7020
|
+
function isIpLiteral(host) {
|
|
7021
|
+
return parseIPv4(host) !== void 0 || parseIPv6(host) !== void 0;
|
|
7022
|
+
}
|
|
7023
|
+
var BLOCKED_V4, BLOCKED_V6;
|
|
7024
|
+
var init_ip_address = __esm({
|
|
7025
|
+
"src/http/ip-address.ts"() {
|
|
7026
|
+
init_reflect_polyfill();
|
|
7027
|
+
BLOCKED_V4 = [
|
|
7028
|
+
[[0, 0, 0, 0], 8],
|
|
7029
|
+
[[10, 0, 0, 0], 8],
|
|
7030
|
+
[[100, 64, 0, 0], 10],
|
|
7031
|
+
[[127, 0, 0, 0], 8],
|
|
7032
|
+
[[169, 254, 0, 0], 16],
|
|
7033
|
+
[[172, 16, 0, 0], 12],
|
|
7034
|
+
[[192, 0, 0, 0], 24],
|
|
7035
|
+
[[192, 0, 2, 0], 24],
|
|
7036
|
+
[[192, 88, 99, 0], 24],
|
|
7037
|
+
[[192, 168, 0, 0], 16],
|
|
7038
|
+
[[198, 18, 0, 0], 15],
|
|
7039
|
+
[[198, 51, 100, 0], 24],
|
|
7040
|
+
[[203, 0, 113, 0], 24],
|
|
7041
|
+
[[224, 0, 0, 0], 4],
|
|
7042
|
+
[[240, 0, 0, 0], 4]
|
|
7043
|
+
];
|
|
7044
|
+
BLOCKED_V6 = [
|
|
7045
|
+
[[1, 0], 64],
|
|
7046
|
+
// 100::/64 discard-only
|
|
7047
|
+
[[32, 1, 0, 0], 32],
|
|
7048
|
+
// 2001::/32 Teredo (embeds an obfuscated IPv4)
|
|
7049
|
+
[[32, 1, 13, 184], 32],
|
|
7050
|
+
// 2001:db8::/32 documentation
|
|
7051
|
+
[[252], 7],
|
|
7052
|
+
// fc00::/7 unique local (incl. fd00:ec2::254 metadata)
|
|
7053
|
+
[[254, 128], 10],
|
|
7054
|
+
// fe80::/10 link-local
|
|
7055
|
+
[[254, 192], 10],
|
|
7056
|
+
// fec0::/10 deprecated site-local
|
|
7057
|
+
[[255], 8]
|
|
7058
|
+
// ff00::/8 multicast
|
|
7059
|
+
];
|
|
7060
|
+
}
|
|
7061
|
+
});
|
|
7062
|
+
|
|
7063
|
+
// src/http/guarded-fetch.ts
|
|
7064
|
+
function loadDns() {
|
|
7065
|
+
dnsModule ??= (async () => {
|
|
7066
|
+
try {
|
|
7067
|
+
const specifier = "node:dns";
|
|
7068
|
+
const mod = await import(
|
|
7069
|
+
/* webpackIgnore: true */
|
|
7070
|
+
/* @vite-ignore */
|
|
7071
|
+
specifier
|
|
7072
|
+
);
|
|
7073
|
+
return mod.promises ?? mod.default?.promises;
|
|
7074
|
+
} catch {
|
|
7075
|
+
return void 0;
|
|
7076
|
+
}
|
|
7077
|
+
})();
|
|
7078
|
+
return dnsModule;
|
|
7079
|
+
}
|
|
7080
|
+
function toError(err) {
|
|
7081
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
7082
|
+
}
|
|
7083
|
+
function createGuardedFetcher(options = {}) {
|
|
7084
|
+
const allowHttp = options.allowHttp ?? false;
|
|
7085
|
+
const allowPrivate = options.allowPrivateNetworks ?? false;
|
|
7086
|
+
const maxRedirects = options.maxRedirects ?? GUARDED_FETCH_DEFAULTS.maxRedirects;
|
|
7087
|
+
const timeoutMs = options.timeoutMs ?? GUARDED_FETCH_DEFAULTS.timeoutMs;
|
|
7088
|
+
const maxBytes = options.maxResponseBytes ?? GUARDED_FETCH_DEFAULTS.maxResponseBytes;
|
|
7089
|
+
const lookup = options.lookup ?? defaultLookup;
|
|
7090
|
+
const transport = options.fetch ?? ((url, init) => globalThis.fetch(url, init));
|
|
7091
|
+
return async (input, init = {}) => {
|
|
7092
|
+
const controller = new AbortController();
|
|
7093
|
+
const timer = setTimeout(() => {
|
|
7094
|
+
controller.abort(
|
|
7095
|
+
new GuardedFetchError("timeout", `request to ${input} exceeded ${timeoutMs} ms`, { url: input })
|
|
7096
|
+
);
|
|
7097
|
+
}, timeoutMs);
|
|
7098
|
+
const callerSignal = init.signal ?? void 0;
|
|
7099
|
+
const onCallerAbort = () => controller.abort(callerSignal?.reason);
|
|
7100
|
+
if (callerSignal?.aborted) onCallerAbort();
|
|
7101
|
+
else callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
7102
|
+
const aborted = new Promise((_, reject) => {
|
|
7103
|
+
const fail = () => reject(toError(controller.signal.reason));
|
|
7104
|
+
if (controller.signal.aborted) fail();
|
|
7105
|
+
else controller.signal.addEventListener("abort", fail, { once: true });
|
|
7106
|
+
});
|
|
7107
|
+
aborted.catch(() => {
|
|
7108
|
+
});
|
|
7109
|
+
const race = (p) => Promise.race([p, aborted]);
|
|
7110
|
+
try {
|
|
7111
|
+
let url = await race(validateTarget(input, void 0));
|
|
7112
|
+
let method = (init.method ?? "GET").toUpperCase();
|
|
7113
|
+
let body = init.body;
|
|
7114
|
+
const headers = new Headers(init.headers);
|
|
7115
|
+
for (let hop = 0; ; hop++) {
|
|
7116
|
+
const response = await race(
|
|
7117
|
+
transport(url.href, { ...init, method, body, headers, redirect: "manual", signal: controller.signal })
|
|
7118
|
+
);
|
|
7119
|
+
if (response.type === "opaqueredirect") {
|
|
7120
|
+
throw new GuardedFetchError(
|
|
7121
|
+
"invalid_redirect",
|
|
7122
|
+
`redirect from ${url.href} cannot be inspected by this transport`,
|
|
7123
|
+
{ url: url.href }
|
|
7124
|
+
);
|
|
7125
|
+
}
|
|
7126
|
+
if (!REDIRECT_STATUSES.has(response.status)) {
|
|
7127
|
+
const bytes = await readBounded(response, url.href, maxBytes, race);
|
|
7128
|
+
return new Response(NULL_BODY_STATUSES.has(response.status) ? null : bytes, {
|
|
7129
|
+
status: response.status,
|
|
7130
|
+
statusText: response.statusText,
|
|
7131
|
+
headers: response.headers
|
|
7132
|
+
});
|
|
7133
|
+
}
|
|
7134
|
+
await response.body?.cancel().catch(() => {
|
|
7135
|
+
});
|
|
7136
|
+
if (hop >= maxRedirects) {
|
|
7137
|
+
throw new GuardedFetchError(
|
|
7138
|
+
"too_many_redirects",
|
|
7139
|
+
`more than ${maxRedirects} redirects starting at ${input}`,
|
|
7140
|
+
{ url: url.href }
|
|
7141
|
+
);
|
|
7142
|
+
}
|
|
7143
|
+
const location = response.headers.get("location");
|
|
7144
|
+
if (!location) {
|
|
7145
|
+
throw new GuardedFetchError(
|
|
7146
|
+
"invalid_redirect",
|
|
7147
|
+
`HTTP ${response.status} from ${url.href} has no Location header`,
|
|
7148
|
+
{ url: url.href }
|
|
7149
|
+
);
|
|
7150
|
+
}
|
|
7151
|
+
let next;
|
|
7152
|
+
try {
|
|
7153
|
+
next = new URL(location, url);
|
|
7154
|
+
} catch (err) {
|
|
7155
|
+
throw new GuardedFetchError("invalid_redirect", `unparseable Location from ${url.href}`, {
|
|
7156
|
+
url: url.href,
|
|
7157
|
+
cause: toError(err)
|
|
7158
|
+
});
|
|
7159
|
+
}
|
|
7160
|
+
const nextUrl = await race(validateTarget(next.href, url));
|
|
7161
|
+
if (response.status === 303 || (response.status === 301 || response.status === 302) && method === "POST") {
|
|
7162
|
+
if (method !== "HEAD") method = "GET";
|
|
7163
|
+
body = void 0;
|
|
7164
|
+
for (const h of BODY_HEADERS) headers.delete(h);
|
|
7165
|
+
}
|
|
7166
|
+
if (nextUrl.origin !== url.origin) {
|
|
7167
|
+
for (const h of CROSS_ORIGIN_STRIPPED_HEADERS) headers.delete(h);
|
|
7168
|
+
}
|
|
7169
|
+
url = nextUrl;
|
|
7170
|
+
}
|
|
7171
|
+
} catch (err) {
|
|
7172
|
+
if (controller.signal.aborted) throw toError(controller.signal.reason);
|
|
7173
|
+
throw err;
|
|
7174
|
+
} finally {
|
|
7175
|
+
clearTimeout(timer);
|
|
7176
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
7177
|
+
}
|
|
7178
|
+
};
|
|
7179
|
+
async function validateTarget(raw, previous) {
|
|
7180
|
+
let url;
|
|
7181
|
+
try {
|
|
7182
|
+
url = new URL(raw);
|
|
7183
|
+
} catch (err) {
|
|
7184
|
+
throw new GuardedFetchError("invalid_url", `not an absolute URL: ${raw}`, { url: raw, cause: toError(err) });
|
|
7185
|
+
}
|
|
7186
|
+
if (url.protocol !== "https:" && !(allowHttp && url.protocol === "http:")) {
|
|
7187
|
+
throw new GuardedFetchError("insecure_scheme", `refusing ${url.protocol} URL ${url.href}: HTTPS is required`, {
|
|
7188
|
+
url: url.href
|
|
7189
|
+
});
|
|
7190
|
+
}
|
|
7191
|
+
if (previous?.protocol === "https:" && url.protocol !== "https:") {
|
|
7192
|
+
throw new GuardedFetchError("insecure_scheme", `refusing https -> http redirect to ${url.href}`, {
|
|
7193
|
+
url: url.href
|
|
7194
|
+
});
|
|
7195
|
+
}
|
|
7196
|
+
if (allowPrivate) return url;
|
|
7197
|
+
const host = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
7198
|
+
if (host === "localhost" || host.endsWith(".localhost")) {
|
|
7199
|
+
throw new GuardedFetchError("private_address", `refusing loopback host ${host}`, { url: url.href });
|
|
7200
|
+
}
|
|
7201
|
+
if (isIpLiteral(host)) {
|
|
7202
|
+
if (!isPublicIpAddress(host)) {
|
|
7203
|
+
throw new GuardedFetchError("private_address", `refusing non-public address ${host}`, { url: url.href });
|
|
7204
|
+
}
|
|
7205
|
+
return url;
|
|
7206
|
+
}
|
|
7207
|
+
let addresses;
|
|
7208
|
+
try {
|
|
7209
|
+
addresses = await lookup(host);
|
|
7210
|
+
} catch (err) {
|
|
7211
|
+
throw new GuardedFetchError("dns_resolution_failed", `could not resolve ${host}`, {
|
|
7212
|
+
url: url.href,
|
|
7213
|
+
cause: toError(err)
|
|
7214
|
+
});
|
|
7215
|
+
}
|
|
7216
|
+
if (addresses === void 0) return url;
|
|
7217
|
+
if (addresses.length === 0) {
|
|
7218
|
+
throw new GuardedFetchError("dns_resolution_failed", `${host} resolved to no addresses`, {
|
|
7219
|
+
url: url.href
|
|
7220
|
+
});
|
|
7221
|
+
}
|
|
7222
|
+
const blocked = addresses.find((a) => !isPublicIpAddress(a));
|
|
7223
|
+
if (blocked !== void 0) {
|
|
7224
|
+
throw new GuardedFetchError("private_address", `${host} resolves to non-public address ${blocked}`, {
|
|
7225
|
+
url: url.href
|
|
7226
|
+
});
|
|
7227
|
+
}
|
|
7228
|
+
return url;
|
|
7229
|
+
}
|
|
7230
|
+
}
|
|
7231
|
+
async function readBounded(response, url, maxBytes, race) {
|
|
7232
|
+
const tooLarge = () => new GuardedFetchError("response_too_large", `response from ${url} exceeds ${maxBytes} bytes`, { url });
|
|
7233
|
+
const declared = response.headers.get("content-length");
|
|
7234
|
+
if (declared !== null && Number(declared) > maxBytes) {
|
|
7235
|
+
await response.body?.cancel().catch(() => {
|
|
7236
|
+
});
|
|
7237
|
+
throw tooLarge();
|
|
7238
|
+
}
|
|
7239
|
+
if (!response.body) return null;
|
|
7240
|
+
const reader = response.body.getReader();
|
|
7241
|
+
const chunks = [];
|
|
7242
|
+
let total = 0;
|
|
7243
|
+
try {
|
|
7244
|
+
for (; ; ) {
|
|
7245
|
+
const { done, value } = await race(reader.read());
|
|
7246
|
+
if (done) break;
|
|
7247
|
+
total += value.byteLength;
|
|
7248
|
+
if (total > maxBytes) throw tooLarge();
|
|
7249
|
+
chunks.push(value);
|
|
7250
|
+
}
|
|
7251
|
+
} catch (err) {
|
|
7252
|
+
reader.cancel().catch(() => {
|
|
7253
|
+
});
|
|
7254
|
+
throw err;
|
|
7255
|
+
}
|
|
7256
|
+
const out = new Uint8Array(new ArrayBuffer(total));
|
|
7257
|
+
let offset = 0;
|
|
7258
|
+
for (const chunk of chunks) {
|
|
7259
|
+
out.set(chunk, offset);
|
|
7260
|
+
offset += chunk.byteLength;
|
|
7261
|
+
}
|
|
7262
|
+
return out;
|
|
7263
|
+
}
|
|
7264
|
+
function createDefaultTrustFetcher() {
|
|
7265
|
+
return createGuardedFetcher({ allowHttp: true });
|
|
7266
|
+
}
|
|
7267
|
+
var GUARDED_FETCH_DEFAULTS, REDIRECT_STATUSES, NULL_BODY_STATUSES, CROSS_ORIGIN_STRIPPED_HEADERS, BODY_HEADERS, dnsModule, defaultLookup;
|
|
7268
|
+
var init_guarded_fetch = __esm({
|
|
7269
|
+
"src/http/guarded-fetch.ts"() {
|
|
7270
|
+
init_reflect_polyfill();
|
|
7271
|
+
init_errors();
|
|
7272
|
+
init_ip_address();
|
|
7273
|
+
GUARDED_FETCH_DEFAULTS = {
|
|
7274
|
+
maxRedirects: 3,
|
|
7275
|
+
timeoutMs: 15e3,
|
|
7276
|
+
maxResponseBytes: 16 * 1024 * 1024
|
|
7277
|
+
};
|
|
7278
|
+
REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
7279
|
+
NULL_BODY_STATUSES = /* @__PURE__ */ new Set([101, 103, 204, 205, 304]);
|
|
7280
|
+
CROSS_ORIGIN_STRIPPED_HEADERS = ["authorization", "cookie", "proxy-authorization"];
|
|
7281
|
+
BODY_HEADERS = ["content-type", "content-length", "content-encoding", "content-language", "content-location"];
|
|
7282
|
+
defaultLookup = async (hostname) => {
|
|
7283
|
+
const dns = await loadDns();
|
|
7284
|
+
if (!dns) return void 0;
|
|
7285
|
+
const records = await dns.lookup(hostname, { all: true, verbatim: true });
|
|
7286
|
+
return records.map((r) => r.address);
|
|
7287
|
+
};
|
|
7288
|
+
}
|
|
7289
|
+
});
|
|
7290
|
+
|
|
6823
7291
|
// src/trust/CrlFetcher.ts
|
|
6824
7292
|
function normalizeCrl(parsed, der) {
|
|
6825
7293
|
const thisUpdate = parsed.tbsCertList.thisUpdate.getTime();
|
|
@@ -6855,11 +7323,12 @@ var init_CrlFetcher = __esm({
|
|
|
6855
7323
|
init_es2015();
|
|
6856
7324
|
init_es20152();
|
|
6857
7325
|
init_ecdsa_util();
|
|
7326
|
+
init_guarded_fetch();
|
|
6858
7327
|
CrlFetcher = class {
|
|
6859
7328
|
fetcher;
|
|
6860
7329
|
cache;
|
|
6861
7330
|
constructor(opts = {}) {
|
|
6862
|
-
this.fetcher = opts.fetcher ??
|
|
7331
|
+
this.fetcher = opts.fetcher ?? createDefaultTrustFetcher();
|
|
6863
7332
|
this.cache = opts.cache;
|
|
6864
7333
|
}
|
|
6865
7334
|
async fetchAndParse(url) {
|
|
@@ -7591,12 +8060,13 @@ var init_OcspClient = __esm({
|
|
|
7591
8060
|
init_es20152();
|
|
7592
8061
|
init_ChainBuilder();
|
|
7593
8062
|
init_ecdsa_util();
|
|
8063
|
+
init_guarded_fetch();
|
|
7594
8064
|
DEFAULT_MAX_RESPONSE_AGE_MS = 7 * 24 * 3600 * 1e3;
|
|
7595
8065
|
OcspClient = class {
|
|
7596
8066
|
fetcher;
|
|
7597
8067
|
cache;
|
|
7598
8068
|
constructor(opts = {}) {
|
|
7599
|
-
this.fetcher = opts.fetcher ??
|
|
8069
|
+
this.fetcher = opts.fetcher ?? createDefaultTrustFetcher();
|
|
7600
8070
|
this.cache = opts.cache;
|
|
7601
8071
|
}
|
|
7602
8072
|
/**
|
|
@@ -8062,7 +8532,9 @@ var init_ProvenanceResolver = __esm({
|
|
|
8062
8532
|
// src/trust/TrustEvaluator.ts
|
|
8063
8533
|
var TrustEvaluator_exports = {};
|
|
8064
8534
|
__export(TrustEvaluator_exports, {
|
|
8065
|
-
|
|
8535
|
+
MAX_CREDENTIAL_CERTIFICATES: () => MAX_CREDENTIAL_CERTIFICATES,
|
|
8536
|
+
TrustEvaluator: () => TrustEvaluator,
|
|
8537
|
+
evaluateCredentialIssuer: () => evaluateCredentialIssuer
|
|
8066
8538
|
});
|
|
8067
8539
|
function deriveHint(leaf) {
|
|
8068
8540
|
const aki = leaf.getExtension(AuthorityKeyIdentifierExtension);
|
|
@@ -8073,6 +8545,13 @@ function deriveHint(leaf) {
|
|
|
8073
8545
|
aki: akiBytes
|
|
8074
8546
|
};
|
|
8075
8547
|
}
|
|
8548
|
+
function deriveSelfHint(cert) {
|
|
8549
|
+
const skiHex = getSkiHex(cert);
|
|
8550
|
+
return {
|
|
8551
|
+
issuer: cert.subject,
|
|
8552
|
+
aki: skiHex ? hexToBytes(skiHex) : void 0
|
|
8553
|
+
};
|
|
8554
|
+
}
|
|
8076
8555
|
function hexToBytes(s) {
|
|
8077
8556
|
const clean = s.replace(/[^0-9a-f]/gi, "");
|
|
8078
8557
|
const out = new Uint8Array(clean.length / 2);
|
|
@@ -8104,7 +8583,31 @@ async function resolveProvenance(store, anchor) {
|
|
|
8104
8583
|
return void 0;
|
|
8105
8584
|
}
|
|
8106
8585
|
}
|
|
8107
|
-
|
|
8586
|
+
async function evaluateCredentialIssuer(input) {
|
|
8587
|
+
const [signerDer, ...rest] = input.chain;
|
|
8588
|
+
if (!signerDer) {
|
|
8589
|
+
throw new MalformedCredentialError("Missing issuer certificate");
|
|
8590
|
+
}
|
|
8591
|
+
if (input.chain.length > MAX_CREDENTIAL_CERTIFICATES) {
|
|
8592
|
+
throw new MalformedCredentialError(
|
|
8593
|
+
`x5c/x5chain carries ${input.chain.length} certificates; at most ${MAX_CREDENTIAL_CERTIFICATES} are accepted`
|
|
8594
|
+
);
|
|
8595
|
+
}
|
|
8596
|
+
const parse = (bytes, position) => {
|
|
8597
|
+
try {
|
|
8598
|
+
return new X509Certificate(bytes);
|
|
8599
|
+
} catch (err) {
|
|
8600
|
+
throw new MalformedCredentialError(
|
|
8601
|
+
`Invalid certificate at x5c/x5chain position ${position}: ${err.message}`
|
|
8602
|
+
);
|
|
8603
|
+
}
|
|
8604
|
+
};
|
|
8605
|
+
const leaf = parse(signerDer, 0);
|
|
8606
|
+
const intermediates = rest.map((bytes, i) => parse(bytes, i + 1));
|
|
8607
|
+
const { chain, issuedAt, ...evaluatorOptions } = input;
|
|
8608
|
+
return new TrustEvaluator(evaluatorOptions).evaluate(leaf, { intermediates, issuedAt });
|
|
8609
|
+
}
|
|
8610
|
+
var TrustEvaluator, MAX_CREDENTIAL_CERTIFICATES;
|
|
8108
8611
|
var init_TrustEvaluator = __esm({
|
|
8109
8612
|
"src/trust/TrustEvaluator.ts"() {
|
|
8110
8613
|
init_reflect_polyfill();
|
|
@@ -8130,13 +8633,10 @@ var init_TrustEvaluator = __esm({
|
|
|
8130
8633
|
revocationChecker;
|
|
8131
8634
|
async evaluate(leaf, context = {}) {
|
|
8132
8635
|
const hint = deriveHint(leaf);
|
|
8133
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8134
8636
|
const anchors = [];
|
|
8135
8637
|
const pushUnique = (batch) => {
|
|
8136
8638
|
for (const a of batch) {
|
|
8137
|
-
|
|
8138
|
-
if (seen.has(key)) continue;
|
|
8139
|
-
seen.add(key);
|
|
8639
|
+
if (anchors.some((x) => certificatesEqual(x.certificate, a.certificate))) continue;
|
|
8140
8640
|
anchors.push(a);
|
|
8141
8641
|
}
|
|
8142
8642
|
};
|
|
@@ -8144,6 +8644,7 @@ var init_TrustEvaluator = __esm({
|
|
|
8144
8644
|
for (const inter of context.intermediates ?? []) {
|
|
8145
8645
|
pushUnique(await this.trustStore.getAnchors(deriveHint(inter)));
|
|
8146
8646
|
}
|
|
8647
|
+
pushUnique(await this.trustStore.getAnchors(deriveSelfHint(leaf)));
|
|
8147
8648
|
if (anchors.length === 0) {
|
|
8148
8649
|
throw new TrustAnchorNotFoundError(
|
|
8149
8650
|
`no trust anchor for issuer=${hint.issuer ?? "(none)"}`
|
|
@@ -8153,7 +8654,8 @@ var init_TrustEvaluator = __esm({
|
|
|
8153
8654
|
const chain = await this.chainBuilder.build(
|
|
8154
8655
|
leaf,
|
|
8155
8656
|
anchorCerts,
|
|
8156
|
-
context.intermediates ?? []
|
|
8657
|
+
context.intermediates ?? [],
|
|
8658
|
+
{ issuedAt: context.issuedAt }
|
|
8157
8659
|
);
|
|
8158
8660
|
const terminusCert = chain[chain.length - 1];
|
|
8159
8661
|
const anchor = anchors.find(
|
|
@@ -8193,6 +8695,7 @@ var init_TrustEvaluator = __esm({
|
|
|
8193
8695
|
};
|
|
8194
8696
|
}
|
|
8195
8697
|
};
|
|
8698
|
+
MAX_CREDENTIAL_CERTIFICATES = 10;
|
|
8196
8699
|
}
|
|
8197
8700
|
});
|
|
8198
8701
|
|
|
@@ -8984,6 +9487,14 @@ function bytesEqual3(a, b) {
|
|
|
8984
9487
|
}
|
|
8985
9488
|
return true;
|
|
8986
9489
|
}
|
|
9490
|
+
function peekMsoSigned(payload) {
|
|
9491
|
+
try {
|
|
9492
|
+
const signed = decodeMso(payload).validityInfo.signed;
|
|
9493
|
+
return Number.isFinite(signed.getTime()) ? signed : void 0;
|
|
9494
|
+
} catch {
|
|
9495
|
+
return void 0;
|
|
9496
|
+
}
|
|
9497
|
+
}
|
|
8987
9498
|
function derToPem(der) {
|
|
8988
9499
|
let binary = "";
|
|
8989
9500
|
for (const b of der) binary += String.fromCharCode(b);
|
|
@@ -9144,17 +9655,17 @@ var MdocParser = class {
|
|
|
9144
9655
|
}
|
|
9145
9656
|
let trustResult;
|
|
9146
9657
|
if (options.trustStore) {
|
|
9147
|
-
const {
|
|
9148
|
-
|
|
9658
|
+
const { evaluateCredentialIssuer: evaluateCredentialIssuer2 } = await Promise.resolve().then(() => (init_TrustEvaluator(), TrustEvaluator_exports));
|
|
9659
|
+
trustResult = await evaluateCredentialIssuer2({
|
|
9660
|
+
chain: cose.x5chain,
|
|
9661
|
+
profile: "iso18013-5",
|
|
9662
|
+
issuedAt: peekMsoSigned(cose.payload),
|
|
9149
9663
|
trustStore: options.trustStore,
|
|
9150
9664
|
revocationPolicy: options.revocationPolicy ?? "skip",
|
|
9151
9665
|
fetcher: options.fetcher,
|
|
9152
9666
|
cache: options.cache,
|
|
9153
9667
|
clockSkewTolerance: options.clockSkewTolerance
|
|
9154
9668
|
});
|
|
9155
|
-
const { X509Certificate: X509Certificate9 } = await import('@peculiar/x509');
|
|
9156
|
-
const leaf = new X509Certificate9(issuerCertBytes);
|
|
9157
|
-
trustResult = await evaluator.evaluate(leaf);
|
|
9158
9669
|
} else {
|
|
9159
9670
|
if (options.trustedCertificates.length === 0) {
|
|
9160
9671
|
if (options.skipTrustCheck !== true) {
|
|
@@ -9303,6 +9814,13 @@ var MdocParser = class {
|
|
|
9303
9814
|
init_reflect_polyfill();
|
|
9304
9815
|
init_errors();
|
|
9305
9816
|
var DEFAULT_ALLOWED_ALGORITHMS2 = ["ES256", "ES384", "ES512"];
|
|
9817
|
+
var SD_JWT_VC_TYP = "dc+sd-jwt";
|
|
9818
|
+
var LEGACY_SD_JWT_VC_TYP = "vc+sd-jwt";
|
|
9819
|
+
function normalizeTyp(typ) {
|
|
9820
|
+
if (typeof typ !== "string") return void 0;
|
|
9821
|
+
const lower = typ.toLowerCase();
|
|
9822
|
+
return lower.startsWith("application/") ? lower.slice("application/".length) : lower;
|
|
9823
|
+
}
|
|
9306
9824
|
var sha256Hasher = async (data, _alg) => {
|
|
9307
9825
|
const encoder2 = new TextEncoder();
|
|
9308
9826
|
const input = typeof data === "string" ? encoder2.encode(data) : data;
|
|
@@ -9415,6 +9933,18 @@ var SdJwtParser = class {
|
|
|
9415
9933
|
if (typeof alg !== "string" || !allowedAlgorithms.includes(alg)) {
|
|
9416
9934
|
return invalidResult(`Unsupported algorithm: ${alg}`);
|
|
9417
9935
|
}
|
|
9936
|
+
const typ = normalizeTyp(header.typ);
|
|
9937
|
+
if (typ === LEGACY_SD_JWT_VC_TYP) {
|
|
9938
|
+
if (options.allowLegacyVcSdJwtTyp !== true) {
|
|
9939
|
+
return invalidResult(
|
|
9940
|
+
`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.`
|
|
9941
|
+
);
|
|
9942
|
+
}
|
|
9943
|
+
} else if (typ !== SD_JWT_VC_TYP) {
|
|
9944
|
+
return invalidResult(
|
|
9945
|
+
`Unsupported SD-JWT VC typ: ${header.typ === void 0 ? "(missing)" : String(header.typ)} (expected '${SD_JWT_VC_TYP}')`
|
|
9946
|
+
);
|
|
9947
|
+
}
|
|
9418
9948
|
const x5cArray = header.x5c;
|
|
9419
9949
|
const hasX5c = Array.isArray(x5cArray) && x5cArray.length > 0 && typeof x5cArray[0] === "string";
|
|
9420
9950
|
let issuerKey;
|
|
@@ -9484,17 +10014,22 @@ var SdJwtParser = class {
|
|
|
9484
10014
|
};
|
|
9485
10015
|
let trustResult;
|
|
9486
10016
|
if (issuerCertBytes === void 0) ; else if (options.trustStore) {
|
|
9487
|
-
const {
|
|
9488
|
-
const
|
|
10017
|
+
const { evaluateCredentialIssuer: evaluateCredentialIssuer2 } = await Promise.resolve().then(() => (init_TrustEvaluator(), TrustEvaluator_exports));
|
|
10018
|
+
const x5cRest = x5cArray.slice(1);
|
|
10019
|
+
if (!x5cRest.every((entry) => typeof entry === "string")) {
|
|
10020
|
+
throw new MalformedCredentialError("x5c entries must be base64 strings");
|
|
10021
|
+
}
|
|
10022
|
+
const iat = payload["iat"];
|
|
10023
|
+
trustResult = await evaluateCredentialIssuer2({
|
|
10024
|
+
chain: [issuerCertBytes, ...x5cRest.map((entry) => base64ToBytes(entry))],
|
|
10025
|
+
profile: "generic",
|
|
10026
|
+
issuedAt: typeof iat === "number" && Number.isFinite(iat) ? new Date(iat * 1e3) : void 0,
|
|
9489
10027
|
trustStore: options.trustStore,
|
|
9490
10028
|
revocationPolicy: options.revocationPolicy ?? "skip",
|
|
9491
10029
|
fetcher: options.fetcher,
|
|
9492
10030
|
cache: options.cache,
|
|
9493
10031
|
clockSkewTolerance: options.clockSkewTolerance
|
|
9494
10032
|
});
|
|
9495
|
-
const { X509Certificate: X509Certificate9 } = await import('@peculiar/x509');
|
|
9496
|
-
const leaf = new X509Certificate9(issuerCertBytes);
|
|
9497
|
-
trustResult = await evaluator.evaluate(leaf);
|
|
9498
10033
|
} else {
|
|
9499
10034
|
if (options.trustedCertificates.length === 0) {
|
|
9500
10035
|
if (options.skipTrustCheck !== true) {
|
|
@@ -9930,6 +10465,9 @@ var InMemoryCache = class {
|
|
|
9930
10465
|
}
|
|
9931
10466
|
};
|
|
9932
10467
|
|
|
10468
|
+
// src/index.ts
|
|
10469
|
+
init_guarded_fetch();
|
|
10470
|
+
|
|
9933
10471
|
// src/trust/index.ts
|
|
9934
10472
|
init_reflect_polyfill();
|
|
9935
10473
|
|
|
@@ -9977,14 +10515,10 @@ var CompositeTrustStore = class {
|
|
|
9977
10515
|
const results = await Promise.all(
|
|
9978
10516
|
this.stores.map((store) => store.getAnchors(hint))
|
|
9979
10517
|
);
|
|
9980
|
-
const seen = /* @__PURE__ */ new Set();
|
|
9981
10518
|
const out = [];
|
|
9982
10519
|
for (const batch of results) {
|
|
9983
10520
|
for (const anchor of batch) {
|
|
9984
|
-
|
|
9985
|
-
const key = ski ?? anchor.certificate.serialNumber;
|
|
9986
|
-
if (seen.has(key)) continue;
|
|
9987
|
-
seen.add(key);
|
|
10521
|
+
if (out.some((a) => certificatesEqual(a.certificate, anchor.certificate))) continue;
|
|
9988
10522
|
out.push(anchor);
|
|
9989
10523
|
}
|
|
9990
10524
|
}
|
|
@@ -10078,11 +10612,12 @@ var EU_LOTL_SIGNING_ANCHORS = Object.freeze(
|
|
|
10078
10612
|
// src/trust/LotlFetcher.ts
|
|
10079
10613
|
init_reflect_polyfill();
|
|
10080
10614
|
init_errors();
|
|
10615
|
+
init_guarded_fetch();
|
|
10081
10616
|
var XMLDSIG_NS = "http://www.w3.org/2000/09/xmldsig#";
|
|
10082
10617
|
var LotlFetcher = class {
|
|
10083
10618
|
fetcher;
|
|
10084
10619
|
constructor(opts = {}) {
|
|
10085
|
-
this.fetcher = opts.fetcher ??
|
|
10620
|
+
this.fetcher = opts.fetcher ?? createDefaultTrustFetcher();
|
|
10086
10621
|
}
|
|
10087
10622
|
async fetchSigned(url, signingAnchors) {
|
|
10088
10623
|
assertCryptoEngineRegistered();
|
|
@@ -10544,7 +11079,7 @@ function equalBytes(a, b) {
|
|
|
10544
11079
|
|
|
10545
11080
|
// src/index.ts
|
|
10546
11081
|
init_errors();
|
|
10547
|
-
var VERSION = "0.
|
|
11082
|
+
var VERSION = "0.13.0";
|
|
10548
11083
|
/*! Bundled license information:
|
|
10549
11084
|
|
|
10550
11085
|
pvtsutils/build/index.es.js:
|
|
@@ -10614,6 +11149,6 @@ asn1js/build/index.es.js:
|
|
|
10614
11149
|
*)
|
|
10615
11150
|
*/
|
|
10616
11151
|
|
|
10617
|
-
export { CertificateChainError, CompositeTrustStore, DecryptionFailedError, ExpiredCredentialError, HAIP_DOCTYPE_NAMESPACES, HaipValidationError, InMemoryCache, InvalidSignatureError, LotlConfigurationError, LotlFetchError, LotlSignatureError, LotlTrustStore, MalformedCredentialError, MdocParser, MissingDecryptionKeyError, MissingVerifierEncryptionKeyError, MultipleCredentialsNotSupportedError, NonceValidationError, OpenID4VPError, RevocationCheckFailedError, RevokedCertificateError, SdJwtParser, SignedRequestBuildError, StaticTrustStore, TrustAnchorNotFoundError, UnsupportedFormatError, UnsupportedJweError, VERSION, buildCredentialSetQuery, buildHaipQuery, buildOid4vpSessionTranscript, buildOpenID4VPHandoverSessionTranscript, createAuthorizationRequest, createSignedAuthorizationRequest, decryptAuthorizationResponse, isHaipQuery, parsePresentation, validateCredentialSetQuery, validateHaipQuery, verifyAuthorizationResponse, verifyPresentation };
|
|
11152
|
+
export { CertificateChainError, CompositeTrustStore, DecryptionFailedError, ExpiredCredentialError, GUARDED_FETCH_DEFAULTS, GuardedFetchError, HAIP_DOCTYPE_NAMESPACES, HaipValidationError, InMemoryCache, InvalidSignatureError, LotlConfigurationError, LotlFetchError, LotlSignatureError, LotlTrustStore, MalformedCredentialError, MdocParser, MissingDecryptionKeyError, MissingVerifierEncryptionKeyError, MultipleCredentialsNotSupportedError, NonceValidationError, OpenID4VPError, RevocationCheckFailedError, RevokedCertificateError, SdJwtParser, SignedRequestBuildError, StaticTrustStore, TrustAnchorNotFoundError, UnsupportedFormatError, UnsupportedJweError, VERSION, buildCredentialSetQuery, buildHaipQuery, buildOid4vpSessionTranscript, buildOpenID4VPHandoverSessionTranscript, createAuthorizationRequest, createGuardedFetcher, createSignedAuthorizationRequest, decryptAuthorizationResponse, isHaipQuery, parsePresentation, validateCredentialSetQuery, validateHaipQuery, verifyAuthorizationResponse, verifyPresentation };
|
|
10618
11153
|
//# sourceMappingURL=index.js.map
|
|
10619
11154
|
//# sourceMappingURL=index.js.map
|