@fedify/fedify 2.4.0-dev.2164 → 2.4.0-dev.2190
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/dist/{builder-BqRmnx7p.mjs → builder-CFF0YNTp.mjs} +3 -3
- package/dist/{circuit-breaker-C5NqoLBJ.mjs → circuit-breaker-XJ53md4S.mjs} +16 -11
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-CxDBdtNz.mjs → compound-proof-F2jjkrt0.mjs} +3 -3
- package/dist/{context-BykNUf04.d.ts → context-9T--NSo4.d.ts} +55 -1
- package/dist/{context-L0Fnib9j.d.cts → context-CDcAgm4l.d.cts} +55 -1
- package/dist/{deno-CizGaYeG.mjs → deno-EopndaNb.mjs} +1 -1
- package/dist/{docloader-BBFAItNB.mjs → docloader-u6zzOExF.mjs} +25 -17
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/circuit-breaker.test.mjs +50 -1
- package/dist/federation/compound-fanout.test.mjs +3 -3
- package/dist/federation/handler-compound-proof.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/keycache.test.mjs +1 -1
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +137 -13
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/outgoing-proof.test.mjs +5 -5
- package/dist/federation/portable-actor.test.mjs +3 -3
- package/dist/federation/portable-chain.test.mjs +3 -3
- package/dist/federation/portable-collection.test.mjs +4 -4
- package/dist/federation/portable-gateway-keys.test.mjs +6 -6
- package/dist/federation/portable-inbox.test.mjs +35 -5
- package/dist/federation/portable-parse-uri.test.mjs +1 -1
- package/dist/federation/portable-route-activity.test.d.mts +2 -0
- package/dist/federation/portable-route-activity.test.mjs +259 -0
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-DFCnp94y.cjs → federation-BSS2dUO7.cjs} +38 -21
- package/dist/{http-Baovr6xq.js → http-BqN5kyUf.js} +426 -141
- package/dist/{http-DC1cqAQ2.d.cts → http-BrwKINQn.d.cts} +28 -0
- package/dist/{http-R4g1McwE.cjs → http-CCr2_ipG.cjs} +443 -140
- package/dist/{http-Cok5-vIC.d.ts → http-DGx0KOws.d.ts} +28 -0
- package/dist/{http-DRH0n8TD.mjs → http-Y1ObT1A8.mjs} +180 -92
- package/dist/{key-D5awqp7u.mjs → key-DyXf88bq.mjs} +4 -4
- package/dist/{key-owner-evidence-Bo3yaeL5.mjs → key-owner-evidence-APB6NpSk.mjs} +1 -1
- package/dist/{keycache-PiOM9pM0.mjs → keycache-Da8A8mZa.mjs} +1 -1
- package/dist/{kv-cache-CZiBDLPi.js → kv-cache-C02XitwK.js} +24 -16
- package/dist/{kv-cache-C7RWL7hz.mjs → kv-cache-C8YjNAi_.mjs} +1 -1
- package/dist/{kv-cache-Ct-KWRta.cjs → kv-cache-DUXluAc0.cjs} +23 -15
- package/dist/{ld-CyliEU48.mjs → ld-oQj6j_hP.mjs} +3 -3
- package/dist/{metrics-CFBEehTc.mjs → metrics-zMe834ID.mjs} +1 -1
- package/dist/{middleware-B99JjvFn.mjs → middleware-Bdu2-OPD.mjs} +37 -25
- package/dist/{middleware-CdpTFsa2.js → middleware-CaR2MRWY.js} +39 -22
- package/dist/{middleware-BmqEWsWP.mjs → middleware-Ck9yghxs.mjs} +1 -1
- package/dist/{mod-DK0y9fd-.d.ts → mod-B3OBLj5g.d.ts} +1 -1
- package/dist/{mod-B5z_q9TH.d.ts → mod-C5Mwx5ty.d.ts} +5 -1
- package/dist/{mod--jv6vyrP.d.ts → mod-Ck9_lSJB.d.ts} +1 -1
- package/dist/{mod-BLOXGs6m.d.cts → mod-D58KCqUg.d.cts} +1 -1
- package/dist/{mod-CPPqkyV_.d.cts → mod-DHTuu3l8.d.cts} +1 -1
- package/dist/{mod-BA_F2x53.d.cts → mod-Dp3Ij7sw.d.cts} +5 -1
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +5 -5
- package/dist/mod.d.ts +5 -5
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-DKdtsrEq.mjs → owner-Dem4fbwc.mjs} +5 -5
- package/dist/{portable-collection-uv4CuQXm.js → portable-collection-BEqLrMW3.js} +1 -1
- package/dist/{portable-collection-Cb5cdPIU.mjs → portable-collection-BL6aZlQ0.mjs} +2 -2
- package/dist/{portable-collection-UajqDrSL.cjs → portable-collection-DJe_bk50.cjs} +1 -1
- package/dist/{portable-key-Bv1RdQ0n.mjs → portable-key-Duue9Y2Y.mjs} +209 -56
- package/dist/{portable-key-id-Bg259c4Z.mjs → portable-key-id-Ca30-hOa.mjs} +45 -1
- package/dist/{proof-CRTPWbVh.mjs → proof-C9115DX_.mjs} +5 -5
- package/dist/{send-2dULlZc8.mjs → send-D0hxFFRB.mjs} +3 -3
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +2 -2
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +495 -23
- package/dist/sig/key-owner-evidence.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +1 -1
- package/dist/sig/portable-key-id.test.d.mts +2 -0
- package/dist/sig/portable-key-id.test.mjs +67 -0
- package/dist/sig/portable-key.test.mjs +253 -7
- package/dist/sig/proof.test.mjs +3 -3
- package/dist/{temporal-Dhsk6aUT.mjs → temporal-DKchpKyp.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +52 -0
- package/dist/utils/docloader.test.mjs +93 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +6 -6
|
@@ -14,7 +14,7 @@ import serialize from "json-canon";
|
|
|
14
14
|
import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
|
|
15
15
|
//#region deno.json
|
|
16
16
|
var name = "@fedify/fedify";
|
|
17
|
-
var version = "2.4.0-dev.
|
|
17
|
+
var version = "2.4.0-dev.2190+be3a8be0";
|
|
18
18
|
//#endregion
|
|
19
19
|
//#region src/sig/portable-key-id.ts
|
|
20
20
|
const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
|
|
@@ -115,6 +115,50 @@ function getCanonicalPortableId(id) {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
/**
|
|
118
|
+
* Gets the [FEP-fe34] origin of an ID for same-origin checks. The origin of
|
|
119
|
+
* an `ap:` or `ap+ef61:` URI, or of a compatible identifier on any gateway,
|
|
120
|
+
* is the DID of its canonical portable ID, as FEP-ef61 treats a compatible
|
|
121
|
+
* identifier as the portable ID it stands for; unlike `getFe34Origin()`,
|
|
122
|
+
* which gives a compatible identifier the web origin of its gateway. The
|
|
123
|
+
* origin of any other HTTP(S) URL is its web origin, and that of a DID URL
|
|
124
|
+
* is its DID.
|
|
125
|
+
*
|
|
126
|
+
* Only the parsed `URL` is validated, so dot segments that URL parsing has
|
|
127
|
+
* already resolved cannot be detected here.
|
|
128
|
+
*
|
|
129
|
+
* [FEP-fe34]: https://w3id.org/fep/fe34
|
|
130
|
+
* @returns The origin, or `null` if the ID has none that is supported, or is
|
|
131
|
+
* a malformed portable ID or compatible identifier.
|
|
132
|
+
* @internal
|
|
133
|
+
*/
|
|
134
|
+
function getAuthenticationOrigin(id) {
|
|
135
|
+
try {
|
|
136
|
+
if (isPortableId(id)) {
|
|
137
|
+
const canonical = getCanonicalPortableId(id);
|
|
138
|
+
return canonical == null ? null : getFe34Origin(canonical);
|
|
139
|
+
}
|
|
140
|
+
return getFe34Origin(id);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (error instanceof TypeError) return null;
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Checks whether two IDs identify the same object. Two portable IDs, i.e.,
|
|
148
|
+
* `ap:` or `ap+ef61:` URIs or compatible identifiers, are compared by their
|
|
149
|
+
* canonical portable IDs, so that the same object on different gateways and
|
|
150
|
+
* with different location hints matches. Other IDs have to be equal as
|
|
151
|
+
* they are.
|
|
152
|
+
* @internal
|
|
153
|
+
*/
|
|
154
|
+
function isSameObjectId(a, b) {
|
|
155
|
+
const aPortable = isPortableId(a);
|
|
156
|
+
if (aPortable !== isPortableId(b)) return false;
|
|
157
|
+
if (!aPortable) return a.href === b.href;
|
|
158
|
+
const canonical = getCanonicalPortableId(a);
|
|
159
|
+
return canonical != null && canonical === getCanonicalPortableId(b);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
118
162
|
* Gets the canonical portable ID of an `ap:` or `ap+ef61:` URI or
|
|
119
163
|
* a compatible identifier as it is written, before any URL parsing.
|
|
120
164
|
* Parsing an ID into a `URL` resolves dot segments in its path, which would
|
|
@@ -4533,6 +4577,21 @@ async function fetchPortableGatewayKey(keyId, options = {}) {
|
|
|
4533
4577
|
});
|
|
4534
4578
|
}
|
|
4535
4579
|
/**
|
|
4580
|
+
* The maximum number of `@gateway` location hints followed for one `ap:` or
|
|
4581
|
+
* `ap+ef61:` key ID. Whoever made a signature chooses the hints of its key
|
|
4582
|
+
* ID, so fewer of them are followed than when dereferencing objects, which
|
|
4583
|
+
* bounds the gateways one signature can make Fedify ask. Further hints are
|
|
4584
|
+
* ignored.
|
|
4585
|
+
*/
|
|
4586
|
+
const MAX_KEY_ID_GATEWAY_HINTS = 3;
|
|
4587
|
+
/**
|
|
4588
|
+
* The default of {@link PortableActorKeyOptions.lookupTimeout}, in
|
|
4589
|
+
* milliseconds.
|
|
4590
|
+
*/
|
|
4591
|
+
const DEFAULT_KEY_ID_LOOKUP_TIMEOUT = 1e4;
|
|
4592
|
+
/** The longest delay that `setTimeout()` supports, in milliseconds. */
|
|
4593
|
+
const MAX_TIMEOUT = 2 ** 31 - 1;
|
|
4594
|
+
/**
|
|
4536
4595
|
* Resolves a key of a portable actor at an `ap:` or `ap+ef61:` key ID, e.g.,
|
|
4537
4596
|
* `ap://did:key:z6Mk…/actor#main-key`. Such a key ID names no gateway, so
|
|
4538
4597
|
* the actor's document, i.e., the key ID without its fragment, is fetched
|
|
@@ -4560,12 +4619,25 @@ async function fetchPortableGatewayKey(keyId, options = {}) {
|
|
|
4560
4619
|
* unreachable gateway would have served, so the result is an `unavailable`
|
|
4561
4620
|
* that is not cacheable.
|
|
4562
4621
|
*
|
|
4622
|
+
* Only the first {@link MAX_KEY_ID_GATEWAY_HINTS} location hints are
|
|
4623
|
+
* followed, and all the gateways share one timeout, see
|
|
4624
|
+
* {@link PortableActorKeyOptions.lookupTimeout}. A gateway that does not
|
|
4625
|
+
* serve the document in time failed to serve it, like an unreachable one,
|
|
4626
|
+
* with an error that has no HTTP status. A gateway whose document could not
|
|
4627
|
+
* be processed in time, and the gateways left untried, make the result an
|
|
4628
|
+
* `unavailable` that is not cacheable. A result that arrives after
|
|
4629
|
+
* the timeout is ignored, even if it vouches for the key.
|
|
4630
|
+
*
|
|
4563
4631
|
* @param keyId The key ID, which must be an `ap:` or `ap+ef61:` URI.
|
|
4564
|
-
* @param options Loaders and
|
|
4632
|
+
* @param options Loaders, telemetry providers, and the timeout.
|
|
4565
4633
|
* @returns The resolution, which is never `legacy`.
|
|
4634
|
+
* @throws {RangeError} If the timeout is not a non-negative number of
|
|
4635
|
+
* milliseconds that `setTimeout()` supports.
|
|
4566
4636
|
* @internal
|
|
4567
4637
|
*/
|
|
4568
4638
|
async function resolvePortableActorKey(keyId, options = {}) {
|
|
4639
|
+
const lookupTimeout = options.lookupTimeout ?? DEFAULT_KEY_ID_LOOKUP_TIMEOUT;
|
|
4640
|
+
if (!Number.isFinite(lookupTimeout) || lookupTimeout < 0 || lookupTimeout > MAX_TIMEOUT) throw new RangeError(`The lookup timeout must be between 0 and ${MAX_TIMEOUT} ms: ${lookupTimeout}`);
|
|
4569
4641
|
const documentLoader = options.documentLoader ?? getDocumentLoader();
|
|
4570
4642
|
const contextLoader = options.contextLoader ?? getDocumentLoader();
|
|
4571
4643
|
let formattedKeyId = keyId.href;
|
|
@@ -4584,75 +4656,67 @@ async function resolvePortableActorKey(keyId, options = {}) {
|
|
|
4584
4656
|
if (keyId.hash === "" || keyId.hash === "#") return reject("The key ID has no fragment; only keys embedded in actor documents are supported.");
|
|
4585
4657
|
const actorUrl = new URL(keyId.href);
|
|
4586
4658
|
actorUrl.hash = "";
|
|
4659
|
+
const gateways = getPortableGatewayCandidates(actorUrl);
|
|
4660
|
+
if (gateways.length > MAX_KEY_ID_GATEWAY_HINTS) logger.debug("Following only the first {max} location hints of key {keyId}.", {
|
|
4661
|
+
keyId: formattedKeyId,
|
|
4662
|
+
max: MAX_KEY_ID_GATEWAY_HINTS
|
|
4663
|
+
});
|
|
4587
4664
|
const requestUrls = [];
|
|
4588
|
-
for (const gateway of
|
|
4665
|
+
for (const gateway of gateways.slice(0, MAX_KEY_ID_GATEWAY_HINTS)) try {
|
|
4589
4666
|
requestUrls.push(toCompatibleEf61Id(actorUrl, gateway).href);
|
|
4590
4667
|
} catch (error) {
|
|
4591
4668
|
if (!(error instanceof TypeError)) throw error;
|
|
4592
4669
|
return reject(`The key ID cannot be fetched from gateways: ${error}`);
|
|
4593
4670
|
}
|
|
4594
4671
|
if (requestUrls.length < 1) requestUrls.push(formatIri(actorUrl));
|
|
4672
|
+
const controller = new AbortController();
|
|
4673
|
+
const timeoutError = new DOMException(`Timed out looking up the actor of key ${formattedKeyId} after ${lookupTimeout} ms.`, "TimeoutError");
|
|
4674
|
+
let timer;
|
|
4675
|
+
if (lookupTimeout > 0) timer = setTimeout(() => controller.abort(timeoutError), lookupTimeout);
|
|
4676
|
+
else controller.abort(timeoutError);
|
|
4677
|
+
const { signal } = controller;
|
|
4678
|
+
const attemptOptions = {
|
|
4679
|
+
...options,
|
|
4680
|
+
documentLoader: withDeadline(documentLoader, signal),
|
|
4681
|
+
contextLoader: withDeadline(contextLoader, signal),
|
|
4682
|
+
signal,
|
|
4683
|
+
formattedKeyId
|
|
4684
|
+
};
|
|
4595
4685
|
const attempts = [];
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
} catch (error) {
|
|
4601
|
-
logger.debug("Failed to fetch the actor of key {keyId} from {url}: {error}", {
|
|
4602
|
-
keyId: formattedKeyId,
|
|
4603
|
-
url,
|
|
4604
|
-
error
|
|
4605
|
-
});
|
|
4606
|
-
attempts.push({
|
|
4607
|
-
type: "retrievalFailed",
|
|
4608
|
-
error
|
|
4609
|
-
});
|
|
4610
|
-
continue;
|
|
4611
|
-
}
|
|
4612
|
-
let resolution;
|
|
4613
|
-
try {
|
|
4614
|
-
const object = await Object$1.fromJsonLd(remoteDocument.document, {
|
|
4615
|
-
documentLoader,
|
|
4616
|
-
contextLoader,
|
|
4617
|
-
tracerProvider: options.tracerProvider
|
|
4618
|
-
});
|
|
4619
|
-
resolution = isPortableActorDocument(object) ? await verifyPortableKeyDocument(remoteDocument.document, object, keyId, false, {
|
|
4620
|
-
...options,
|
|
4621
|
-
documentLoader,
|
|
4622
|
-
contextLoader
|
|
4623
|
-
}) : {
|
|
4624
|
-
type: "rejected",
|
|
4625
|
-
reason: "The document is not a portable actor."
|
|
4626
|
-
};
|
|
4627
|
-
} catch (error) {
|
|
4628
|
-
if (error instanceof TypeError) resolution = {
|
|
4629
|
-
type: "rejected",
|
|
4630
|
-
reason: `The document is malformed: ${error.message}`
|
|
4631
|
-
};
|
|
4632
|
-
else {
|
|
4633
|
-
logger.debug("Failed to process the actor of key {keyId} served from {url}: {error}", {
|
|
4686
|
+
try {
|
|
4687
|
+
for (const url of requestUrls) {
|
|
4688
|
+
if (signal.aborted) {
|
|
4689
|
+
logger.debug("Ran out of time looking up the actor of key {keyId}; gateways from {url} on were not tried.", {
|
|
4634
4690
|
keyId: formattedKeyId,
|
|
4635
|
-
url
|
|
4636
|
-
error
|
|
4691
|
+
url
|
|
4637
4692
|
});
|
|
4638
4693
|
attempts.push({
|
|
4639
4694
|
type: "indeterminate",
|
|
4640
|
-
error
|
|
4695
|
+
error: signal.reason
|
|
4696
|
+
});
|
|
4697
|
+
break;
|
|
4698
|
+
}
|
|
4699
|
+
const state = { processing: false };
|
|
4700
|
+
let attempt;
|
|
4701
|
+
try {
|
|
4702
|
+
attempt = await raceAbort(attemptGateway(url, keyId, attemptOptions, state), signal);
|
|
4703
|
+
} catch (error) {
|
|
4704
|
+
if (!signal.aborted) throw error;
|
|
4705
|
+
logger.debug("Ran out of time looking up the actor of key {keyId} at {url}.", {
|
|
4706
|
+
keyId: formattedKeyId,
|
|
4707
|
+
url
|
|
4708
|
+
});
|
|
4709
|
+
attempts.push({
|
|
4710
|
+
type: state.processing ? "indeterminate" : "retrievalFailed",
|
|
4711
|
+
error: signal.reason
|
|
4641
4712
|
});
|
|
4642
4713
|
continue;
|
|
4643
4714
|
}
|
|
4715
|
+
if (attempt.type === "verified") return attempt;
|
|
4716
|
+
attempts.push(attempt);
|
|
4644
4717
|
}
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
logger.debug("The actor document served from {url} does not vouch for key {keyId}: {reason}", {
|
|
4648
|
-
keyId: formattedKeyId,
|
|
4649
|
-
url,
|
|
4650
|
-
reason
|
|
4651
|
-
});
|
|
4652
|
-
attempts.push({
|
|
4653
|
-
type: "rejected",
|
|
4654
|
-
reason
|
|
4655
|
-
});
|
|
4718
|
+
} finally {
|
|
4719
|
+
clearTimeout(timer);
|
|
4656
4720
|
}
|
|
4657
4721
|
const rejections = attempts.flatMap((a) => a.type === "rejected" ? [a.reason] : []);
|
|
4658
4722
|
if (rejections.length === attempts.length) return reject(rejections.join(" "));
|
|
@@ -4673,6 +4737,139 @@ async function resolvePortableActorKey(keyId, options = {}) {
|
|
|
4673
4737
|
};
|
|
4674
4738
|
}
|
|
4675
4739
|
/**
|
|
4740
|
+
* Fetches the actor's document of a key at an `ap:` or `ap+ef61:` key ID from
|
|
4741
|
+
* one gateway, and tells whether it vouches for the key. It never rejects;
|
|
4742
|
+
* the caller races it against the lookup's timeout, and ignores what it
|
|
4743
|
+
* resolves to after the timeout.
|
|
4744
|
+
*/
|
|
4745
|
+
async function attemptGateway(url, keyId, options, state) {
|
|
4746
|
+
const { documentLoader, contextLoader, signal, formattedKeyId } = options;
|
|
4747
|
+
let remoteDocument;
|
|
4748
|
+
try {
|
|
4749
|
+
remoteDocument = await documentLoader(url);
|
|
4750
|
+
} catch (error) {
|
|
4751
|
+
logger.debug("Failed to fetch the actor of key {keyId} from {url}: {error}", {
|
|
4752
|
+
keyId: formattedKeyId,
|
|
4753
|
+
url,
|
|
4754
|
+
error
|
|
4755
|
+
});
|
|
4756
|
+
return {
|
|
4757
|
+
type: "retrievalFailed",
|
|
4758
|
+
error
|
|
4759
|
+
};
|
|
4760
|
+
}
|
|
4761
|
+
if (signal.aborted) return {
|
|
4762
|
+
type: "indeterminate",
|
|
4763
|
+
error: signal.reason
|
|
4764
|
+
};
|
|
4765
|
+
state.processing = true;
|
|
4766
|
+
let resolution;
|
|
4767
|
+
try {
|
|
4768
|
+
const object = await Object$1.fromJsonLd(remoteDocument.document, {
|
|
4769
|
+
documentLoader,
|
|
4770
|
+
contextLoader,
|
|
4771
|
+
tracerProvider: options.tracerProvider
|
|
4772
|
+
});
|
|
4773
|
+
signal.throwIfAborted();
|
|
4774
|
+
resolution = isPortableActorDocument(object) ? await verifyPortableKeyDocument(remoteDocument.document, object, keyId, false, options) : {
|
|
4775
|
+
type: "rejected",
|
|
4776
|
+
reason: "The document is not a portable actor."
|
|
4777
|
+
};
|
|
4778
|
+
} catch (error) {
|
|
4779
|
+
if (error instanceof TypeError && !signal.aborted) resolution = {
|
|
4780
|
+
type: "rejected",
|
|
4781
|
+
reason: `The document is malformed: ${error.message}`
|
|
4782
|
+
};
|
|
4783
|
+
else {
|
|
4784
|
+
logger.debug("Failed to process the actor of key {keyId} served from {url}: {error}", {
|
|
4785
|
+
keyId: formattedKeyId,
|
|
4786
|
+
url,
|
|
4787
|
+
error
|
|
4788
|
+
});
|
|
4789
|
+
return {
|
|
4790
|
+
type: "indeterminate",
|
|
4791
|
+
error
|
|
4792
|
+
};
|
|
4793
|
+
}
|
|
4794
|
+
}
|
|
4795
|
+
if (resolution.type === "verified") return resolution;
|
|
4796
|
+
const reason = resolution.type === "rejected" ? resolution.reason : "The document is not a portable actor.";
|
|
4797
|
+
logger.debug("The actor document served from {url} does not vouch for key {keyId}: {reason}", {
|
|
4798
|
+
keyId: formattedKeyId,
|
|
4799
|
+
url,
|
|
4800
|
+
reason
|
|
4801
|
+
});
|
|
4802
|
+
return {
|
|
4803
|
+
type: "rejected",
|
|
4804
|
+
reason
|
|
4805
|
+
};
|
|
4806
|
+
}
|
|
4807
|
+
/**
|
|
4808
|
+
* Wraps a document loader so that it gives up when the given signal aborts,
|
|
4809
|
+
* as well as when the signal in its own options does. The wrapped loader is
|
|
4810
|
+
* passed a signal that aborts in either case, but in case it ignores that
|
|
4811
|
+
* signal, what it resolves to after the abort is ignored, and the wrapper
|
|
4812
|
+
* rejects with the reason of the abort at once. After the abort, the wrapped
|
|
4813
|
+
* loader is not called at all.
|
|
4814
|
+
*/
|
|
4815
|
+
function withDeadline(loader, signal) {
|
|
4816
|
+
return (url, options) => {
|
|
4817
|
+
const combined = combineSignals(signal, options?.signal);
|
|
4818
|
+
return raceAbort(combined.signal.aborted ? Promise.reject(combined.signal.reason) : new Promise((resolve) => resolve(loader(url, {
|
|
4819
|
+
...options,
|
|
4820
|
+
signal: combined.signal
|
|
4821
|
+
}))), combined.signal).finally(combined.dispose);
|
|
4822
|
+
};
|
|
4823
|
+
}
|
|
4824
|
+
/**
|
|
4825
|
+
* Makes a signal that aborts when either of the given signals does, with
|
|
4826
|
+
* the reason of the one that aborted first. `AbortSignal.any()` is not
|
|
4827
|
+
* available on every supported runtime, e.g., Bun before 1.1.4.
|
|
4828
|
+
* @returns The signal, and a function that stops listening to the given
|
|
4829
|
+
* signals.
|
|
4830
|
+
*/
|
|
4831
|
+
function combineSignals(signal, other) {
|
|
4832
|
+
if (other == null || other === signal) return {
|
|
4833
|
+
signal,
|
|
4834
|
+
dispose() {}
|
|
4835
|
+
};
|
|
4836
|
+
const controller = new AbortController();
|
|
4837
|
+
const sources = [signal, other];
|
|
4838
|
+
const dispose = () => {
|
|
4839
|
+
for (const source of sources) source.removeEventListener("abort", onAbort);
|
|
4840
|
+
};
|
|
4841
|
+
function onAbort() {
|
|
4842
|
+
dispose();
|
|
4843
|
+
controller.abort(this.reason);
|
|
4844
|
+
}
|
|
4845
|
+
const aborted = sources.find((source) => source.aborted);
|
|
4846
|
+
if (aborted == null) for (const source of sources) source.addEventListener("abort", onAbort);
|
|
4847
|
+
else controller.abort(aborted.reason);
|
|
4848
|
+
return {
|
|
4849
|
+
signal: controller.signal,
|
|
4850
|
+
dispose
|
|
4851
|
+
};
|
|
4852
|
+
}
|
|
4853
|
+
/**
|
|
4854
|
+
* Waits for the given promise unless the given signal aborts first, in which
|
|
4855
|
+
* case it rejects with the reason of the abort. If the signal has aborted by
|
|
4856
|
+
* the time the promise settles, the abort wins as well, so that nothing that
|
|
4857
|
+
* settles after it counts.
|
|
4858
|
+
*/
|
|
4859
|
+
function raceAbort(promise, signal) {
|
|
4860
|
+
return new Promise((resolve, reject) => {
|
|
4861
|
+
const onAbort = () => reject(signal.reason);
|
|
4862
|
+
const settle = (settled) => {
|
|
4863
|
+
signal.removeEventListener("abort", onAbort);
|
|
4864
|
+
if (signal.aborted) onAbort();
|
|
4865
|
+
else settled();
|
|
4866
|
+
};
|
|
4867
|
+
promise.then((value) => settle(() => resolve(value)), (error) => settle(() => reject(error)));
|
|
4868
|
+
if (signal.aborted) onAbort();
|
|
4869
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
4870
|
+
});
|
|
4871
|
+
}
|
|
4872
|
+
/**
|
|
4676
4873
|
* Finds the nodes that stand for the key with the given canonical portable
|
|
4677
4874
|
* ID. A node whose ID is an `ap:` or `ap+ef61:` URI stands for the key
|
|
4678
4875
|
* itself, e.g., Mitra lists the keys of portable actors that way while
|
|
@@ -4903,16 +5100,19 @@ function parseDraftSignature(signature) {
|
|
|
4903
5100
|
* Lists the HTTP Signatures that a request claims to carry, without verifying
|
|
4904
5101
|
* any of them. Signatures whose key IDs are not URLs are left out.
|
|
4905
5102
|
* @param request The request.
|
|
5103
|
+
* @param maxSignatures The maximum number of RFC 9421 signatures to consider,
|
|
5104
|
+
* counted the same way as by {@link verifyRequest}, so
|
|
5105
|
+
* that the signatures it ignores are not listed either.
|
|
4906
5106
|
* @returns The signatures; RFC 9421 ones if the request has
|
|
4907
5107
|
* a `Signature-Input` header, or a draft-cavage one otherwise.
|
|
4908
5108
|
* @internal
|
|
4909
5109
|
*/
|
|
4910
|
-
function listRequestSignatures(request) {
|
|
5110
|
+
function listRequestSignatures(request, maxSignatures = 3) {
|
|
4911
5111
|
const signatureInput = request.headers.get("Signature-Input");
|
|
4912
5112
|
if (signatureInput != null) {
|
|
4913
5113
|
const signatures = [];
|
|
4914
|
-
const inputs = parseRfc9421SignatureInput(signatureInput);
|
|
4915
|
-
for (const [label, input] of
|
|
5114
|
+
const inputs = Object.entries(parseRfc9421SignatureInput(signatureInput));
|
|
5115
|
+
for (const [label, input] of inputs.slice(0, maxSignatures)) {
|
|
4916
5116
|
const keyId = parseKeyId(input.keyId);
|
|
4917
5117
|
if (keyId == null) continue;
|
|
4918
5118
|
signatures.push({
|
|
@@ -5040,6 +5240,18 @@ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime,
|
|
|
5040
5240
|
body
|
|
5041
5241
|
});
|
|
5042
5242
|
}
|
|
5243
|
+
/**
|
|
5244
|
+
* Checks that a maximum number of signatures to verify is a positive safe
|
|
5245
|
+
* integer or `Infinity`.
|
|
5246
|
+
* @param value The value to check.
|
|
5247
|
+
* @param name The name of the option, used in the error message.
|
|
5248
|
+
* @throws {RangeError} Thrown if the value is not valid.
|
|
5249
|
+
* @internal
|
|
5250
|
+
*/
|
|
5251
|
+
function validateMaxSignatures(value, name) {
|
|
5252
|
+
if (value === Infinity || Number.isSafeInteger(value) && value >= 1) return;
|
|
5253
|
+
throw new RangeError(`${name} must be a positive integer or Infinity.`);
|
|
5254
|
+
}
|
|
5043
5255
|
const supportedHashAlgorithms = {
|
|
5044
5256
|
"sha": "SHA-1",
|
|
5045
5257
|
"sha-256": "SHA-256",
|
|
@@ -5132,6 +5344,8 @@ function recordVerificationResult(span, result) {
|
|
|
5132
5344
|
* @param options Options for verifying the request.
|
|
5133
5345
|
* @returns The public key of the verified signature, or `null` if the signature
|
|
5134
5346
|
* could not be verified.
|
|
5347
|
+
* @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
|
|
5348
|
+
* is not a positive integer or `Infinity`.
|
|
5135
5349
|
*/
|
|
5136
5350
|
async function verifyRequest(request, options = {}) {
|
|
5137
5351
|
const result = await verifyRequestDetailed(request, options);
|
|
@@ -5144,9 +5358,12 @@ async function verifyRequest(request, options = {}) {
|
|
|
5144
5358
|
* @param request The request to verify.
|
|
5145
5359
|
* @param options Options for verifying the request.
|
|
5146
5360
|
* @returns The verified public key, or a structured verification failure.
|
|
5361
|
+
* @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
|
|
5362
|
+
* is not a positive integer or `Infinity`.
|
|
5147
5363
|
* @since 2.1.0
|
|
5148
5364
|
*/
|
|
5149
5365
|
async function verifyRequestDetailed(request, options = {}) {
|
|
5366
|
+
if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
|
|
5150
5367
|
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
|
|
5151
5368
|
if (span.isRecording()) {
|
|
5152
5369
|
span.setAttribute(ATTR_HTTP_REQUEST_METHOD, request.method);
|
|
@@ -5455,7 +5672,100 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
|
|
|
5455
5672
|
}
|
|
5456
5673
|
return false;
|
|
5457
5674
|
}
|
|
5458
|
-
|
|
5675
|
+
/**
|
|
5676
|
+
* Verifies one RFC 9421 signature of a request with a key.
|
|
5677
|
+
* @param request The request, whose body may be consumed already.
|
|
5678
|
+
* @param sigInput The parsed `Signature-Input` member of the signature.
|
|
5679
|
+
* @param sigBytes The bytes of the signature.
|
|
5680
|
+
* @param key The public key named by the signature.
|
|
5681
|
+
* @param span The span to record the algorithm and the signature on.
|
|
5682
|
+
* @returns Whether the signature is verified, and the algorithm of the
|
|
5683
|
+
* signature if it is supported.
|
|
5684
|
+
*/
|
|
5685
|
+
async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
|
|
5686
|
+
const logger = getLogger([
|
|
5687
|
+
"fedify",
|
|
5688
|
+
"sig",
|
|
5689
|
+
"http"
|
|
5690
|
+
]);
|
|
5691
|
+
let alg = sigInput.alg?.toLowerCase();
|
|
5692
|
+
if (alg == null) {
|
|
5693
|
+
if (key.publicKey.algorithm.name === "RSASSA-PKCS1-v1_5") alg = "hash" in key.publicKey.algorithm ? key.publicKey.algorithm.hash === "SHA-512" ? "rsa-v1_5-sha512" : "rsa-v1_5-sha256" : "rsa-v1_5-sha256";
|
|
5694
|
+
else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
|
|
5695
|
+
else if (key.publicKey.algorithm.name === "ECDSA") alg = "namedCurve" in key.publicKey.algorithm && key.publicKey.algorithm.namedCurve === "P-256" ? "ecdsa-p256-sha256" : "ecdsa-p384-sha384";
|
|
5696
|
+
else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
|
|
5697
|
+
}
|
|
5698
|
+
if (alg) span?.setAttribute("http_signatures.algorithm", alg);
|
|
5699
|
+
const algorithm = alg && rfc9421AlgorithmMap[alg];
|
|
5700
|
+
if (!algorithm) {
|
|
5701
|
+
logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
|
|
5702
|
+
algorithm: sigInput.alg,
|
|
5703
|
+
supported: Object.keys(rfc9421AlgorithmMap)
|
|
5704
|
+
});
|
|
5705
|
+
return {
|
|
5706
|
+
verified: false,
|
|
5707
|
+
mismatched: false
|
|
5708
|
+
};
|
|
5709
|
+
}
|
|
5710
|
+
const metricAlgorithm = alg;
|
|
5711
|
+
let signatureBase;
|
|
5712
|
+
try {
|
|
5713
|
+
signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
|
|
5714
|
+
} catch (error) {
|
|
5715
|
+
logger.debug("Failed to create signature base for verification: {error}", {
|
|
5716
|
+
error,
|
|
5717
|
+
signatureInput: sigInput
|
|
5718
|
+
});
|
|
5719
|
+
return {
|
|
5720
|
+
verified: false,
|
|
5721
|
+
algorithm: metricAlgorithm,
|
|
5722
|
+
mismatched: false
|
|
5723
|
+
};
|
|
5724
|
+
}
|
|
5725
|
+
const signatureBaseBytes = new TextEncoder().encode(signatureBase);
|
|
5726
|
+
span?.setAttribute("http_signatures.signature", encodeHex(sigBytes));
|
|
5727
|
+
const algorithmName = typeof algorithm === "string" ? algorithm : algorithm.name;
|
|
5728
|
+
if (key.publicKey.algorithm.name !== algorithmName) {
|
|
5729
|
+
logger.debug("Failed to verify; key {keyId} is not a key for algorithm {algorithm}.", {
|
|
5730
|
+
keyId: sigInput.keyId,
|
|
5731
|
+
algorithm: alg
|
|
5732
|
+
});
|
|
5733
|
+
return {
|
|
5734
|
+
verified: false,
|
|
5735
|
+
algorithm: metricAlgorithm,
|
|
5736
|
+
mismatched: true
|
|
5737
|
+
};
|
|
5738
|
+
}
|
|
5739
|
+
let verified;
|
|
5740
|
+
try {
|
|
5741
|
+
verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
|
|
5742
|
+
} catch (error) {
|
|
5743
|
+
logger.debug("Error during signature verification: {error}", {
|
|
5744
|
+
error,
|
|
5745
|
+
keyId: sigInput.keyId,
|
|
5746
|
+
algorithm: sigInput.alg
|
|
5747
|
+
});
|
|
5748
|
+
return {
|
|
5749
|
+
verified: false,
|
|
5750
|
+
algorithm: metricAlgorithm,
|
|
5751
|
+
mismatched: false
|
|
5752
|
+
};
|
|
5753
|
+
}
|
|
5754
|
+
if (verified) return {
|
|
5755
|
+
verified: true,
|
|
5756
|
+
algorithm: metricAlgorithm
|
|
5757
|
+
};
|
|
5758
|
+
logger.debug("Failed to verify signature with key {keyId}; signature invalid.", {
|
|
5759
|
+
keyId: sigInput.keyId,
|
|
5760
|
+
signatureBase
|
|
5761
|
+
});
|
|
5762
|
+
return {
|
|
5763
|
+
verified: false,
|
|
5764
|
+
algorithm: metricAlgorithm,
|
|
5765
|
+
mismatched: true
|
|
5766
|
+
};
|
|
5767
|
+
}
|
|
5768
|
+
async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, meterProvider, tracerProvider } = {}) {
|
|
5459
5769
|
const logger = getLogger([
|
|
5460
5770
|
"fedify",
|
|
5461
5771
|
"sig",
|
|
@@ -5468,7 +5778,6 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
5468
5778
|
logger.error("Failed to verify; the request body is locked.", { url: request.url });
|
|
5469
5779
|
return noSignatureResult();
|
|
5470
5780
|
}
|
|
5471
|
-
const originalRequest = request;
|
|
5472
5781
|
request = request.clone();
|
|
5473
5782
|
const signatureInputHeader = request.headers.get("Signature-Input");
|
|
5474
5783
|
if (!signatureInputHeader) {
|
|
@@ -5488,6 +5797,29 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
5488
5797
|
logger.debug("Failed to verify; no valid signatures found in Signature-Input header.", { header: signatureInputHeader });
|
|
5489
5798
|
return invalidSignatureResult(null);
|
|
5490
5799
|
}
|
|
5800
|
+
if (signatureNames.length > maxSignatures) {
|
|
5801
|
+
logger.debug("Verifying only the first {maxSignatures} of the {count} signatures in the Signature-Input header; the rest are ignored.", {
|
|
5802
|
+
maxSignatures,
|
|
5803
|
+
count: signatureNames.length
|
|
5804
|
+
});
|
|
5805
|
+
signatureNames.splice(maxSignatures);
|
|
5806
|
+
}
|
|
5807
|
+
const keyLookups = /* @__PURE__ */ new Map();
|
|
5808
|
+
const refreshedKeyIds = /* @__PURE__ */ new Set();
|
|
5809
|
+
const lookUpKey = (keyId, keyCache) => measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, CryptographicKey, {
|
|
5810
|
+
documentLoader,
|
|
5811
|
+
contextLoader,
|
|
5812
|
+
keyCache,
|
|
5813
|
+
tracerProvider,
|
|
5814
|
+
meterProvider,
|
|
5815
|
+
portableKeyResolvers: createPortableKeyResolvers({
|
|
5816
|
+
documentLoader,
|
|
5817
|
+
contextLoader,
|
|
5818
|
+
keyCache,
|
|
5819
|
+
tracerProvider,
|
|
5820
|
+
meterProvider
|
|
5821
|
+
})
|
|
5822
|
+
}));
|
|
5491
5823
|
let failure = noSignatureResult();
|
|
5492
5824
|
let failureAlgorithm;
|
|
5493
5825
|
const setFailure = (result, algorithm) => {
|
|
@@ -5561,94 +5893,40 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
5561
5893
|
setFailure(invalidSignatureResult(null));
|
|
5562
5894
|
continue;
|
|
5563
5895
|
}
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
tracerProvider,
|
|
5569
|
-
meterProvider,
|
|
5570
|
-
portableKeyResolvers: createPortableKeyResolvers({
|
|
5571
|
-
documentLoader,
|
|
5572
|
-
contextLoader,
|
|
5573
|
-
keyCache,
|
|
5574
|
-
tracerProvider,
|
|
5575
|
-
meterProvider
|
|
5576
|
-
})
|
|
5577
|
-
}));
|
|
5578
|
-
if (fetchError != null) {
|
|
5579
|
-
setFailure(keyFetchErrorResult(keyId, fetchError));
|
|
5580
|
-
continue;
|
|
5581
|
-
}
|
|
5582
|
-
if (!key) {
|
|
5583
|
-
logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
|
|
5584
|
-
setFailure(invalidSignatureResult(keyId));
|
|
5585
|
-
continue;
|
|
5586
|
-
}
|
|
5587
|
-
let alg = sigInput.alg?.toLowerCase();
|
|
5588
|
-
if (alg == null) {
|
|
5589
|
-
if (key.publicKey.algorithm.name === "RSASSA-PKCS1-v1_5") alg = "hash" in key.publicKey.algorithm ? key.publicKey.algorithm.hash === "SHA-512" ? "rsa-v1_5-sha512" : "rsa-v1_5-sha256" : "rsa-v1_5-sha256";
|
|
5590
|
-
else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
|
|
5591
|
-
else if (key.publicKey.algorithm.name === "ECDSA") alg = "namedCurve" in key.publicKey.algorithm && key.publicKey.algorithm.namedCurve === "P-256" ? "ecdsa-p256-sha256" : "ecdsa-p384-sha384";
|
|
5592
|
-
else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
|
|
5593
|
-
}
|
|
5594
|
-
if (alg) span?.setAttribute("http_signatures.algorithm", alg);
|
|
5595
|
-
const algorithm = alg && rfc9421AlgorithmMap[alg];
|
|
5596
|
-
const candidateAlgorithm = algorithm ? alg : void 0;
|
|
5597
|
-
if (!algorithm) {
|
|
5598
|
-
logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
|
|
5599
|
-
algorithm: sigInput.alg,
|
|
5600
|
-
supported: Object.keys(rfc9421AlgorithmMap)
|
|
5601
|
-
});
|
|
5602
|
-
setFailure(invalidSignatureResult(keyId));
|
|
5603
|
-
continue;
|
|
5604
|
-
}
|
|
5605
|
-
let signatureBase;
|
|
5606
|
-
try {
|
|
5607
|
-
signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
|
|
5608
|
-
} catch (error) {
|
|
5609
|
-
logger.debug("Failed to create signature base for verification: {error}", {
|
|
5610
|
-
error,
|
|
5611
|
-
signatureInput: sigInput
|
|
5612
|
-
});
|
|
5613
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
5614
|
-
continue;
|
|
5896
|
+
let lookup = keyLookups.get(keyId.href);
|
|
5897
|
+
if (lookup == null) {
|
|
5898
|
+
lookup = await lookUpKey(keyId, keyCache);
|
|
5899
|
+
keyLookups.set(keyId.href, lookup);
|
|
5615
5900
|
}
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5901
|
+
while (true) {
|
|
5902
|
+
const { key, cached, fetchError } = lookup;
|
|
5903
|
+
if (fetchError != null) {
|
|
5904
|
+
setFailure(keyFetchErrorResult(keyId, fetchError));
|
|
5905
|
+
break;
|
|
5906
|
+
}
|
|
5907
|
+
if (!key) {
|
|
5908
|
+
logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
|
|
5909
|
+
setFailure(invalidSignatureResult(keyId));
|
|
5910
|
+
break;
|
|
5911
|
+
}
|
|
5912
|
+
const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span);
|
|
5913
|
+
if (result.verified) {
|
|
5914
|
+
metricsContext.algorithm = result.algorithm;
|
|
5621
5915
|
return {
|
|
5622
5916
|
verified: true,
|
|
5623
5917
|
key,
|
|
5624
5918
|
signatureLabel: sigName
|
|
5625
5919
|
};
|
|
5626
|
-
}
|
|
5920
|
+
}
|
|
5921
|
+
if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
|
|
5627
5922
|
logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
currentTime,
|
|
5633
|
-
keyCache: bypassKeyCacheReads(keyCache),
|
|
5634
|
-
spec: "rfc9421",
|
|
5635
|
-
meterProvider,
|
|
5636
|
-
tracerProvider
|
|
5637
|
-
});
|
|
5638
|
-
} else {
|
|
5639
|
-
logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
|
|
5640
|
-
keyId: sigInput.keyId,
|
|
5641
|
-
signatureBase
|
|
5642
|
-
});
|
|
5643
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
5923
|
+
refreshedKeyIds.add(keyId.href);
|
|
5924
|
+
lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
|
|
5925
|
+
keyLookups.set(keyId.href, lookup);
|
|
5926
|
+
continue;
|
|
5644
5927
|
}
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
error,
|
|
5648
|
-
keyId: sigInput.keyId,
|
|
5649
|
-
algorithm: sigInput.alg
|
|
5650
|
-
});
|
|
5651
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
5928
|
+
setFailure(invalidSignatureResult(keyId), result.algorithm);
|
|
5929
|
+
break;
|
|
5652
5930
|
}
|
|
5653
5931
|
}
|
|
5654
5932
|
metricsContext.algorithm = failureAlgorithm;
|
|
@@ -5748,16 +6026,19 @@ async function doubleKnock(request, identity, options = {}) {
|
|
|
5748
6026
|
}
|
|
5749
6027
|
async function doubleKnockInternal(request, identity, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
5750
6028
|
const { specDeterminer, log, tracerProvider, signal } = options;
|
|
6029
|
+
signal?.throwIfAborted();
|
|
5751
6030
|
const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
5752
6031
|
visited.add(request.url);
|
|
5753
6032
|
const origin = new URL(request.url).origin;
|
|
5754
6033
|
const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
|
|
6034
|
+
signal?.throwIfAborted();
|
|
5755
6035
|
const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
|
|
5756
6036
|
let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
5757
6037
|
spec: firstTrySpec,
|
|
5758
6038
|
tracerProvider,
|
|
5759
6039
|
body
|
|
5760
6040
|
});
|
|
6041
|
+
signal?.throwIfAborted();
|
|
5761
6042
|
log?.(signedRequest);
|
|
5762
6043
|
let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
5763
6044
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
@@ -5782,6 +6063,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
5782
6063
|
let fulfilled = false;
|
|
5783
6064
|
let challengeRequest;
|
|
5784
6065
|
for (const entry of entries) {
|
|
6066
|
+
signal?.throwIfAborted();
|
|
5785
6067
|
const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
|
|
5786
6068
|
if (rfc9421 == null) continue;
|
|
5787
6069
|
logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
|
|
@@ -5804,6 +6086,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
5804
6086
|
}
|
|
5805
6087
|
}
|
|
5806
6088
|
if (fulfilled && challengeRequest != null) {
|
|
6089
|
+
signal?.throwIfAborted();
|
|
5807
6090
|
signedRequest = challengeRequest;
|
|
5808
6091
|
log?.(signedRequest);
|
|
5809
6092
|
response = await fetch(signedRequest, {
|
|
@@ -5828,11 +6111,13 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
5828
6111
|
status: response.status,
|
|
5829
6112
|
statusText: response.statusText
|
|
5830
6113
|
});
|
|
6114
|
+
signal?.throwIfAborted();
|
|
5831
6115
|
signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
5832
6116
|
spec,
|
|
5833
6117
|
tracerProvider,
|
|
5834
6118
|
body
|
|
5835
6119
|
});
|
|
6120
|
+
signal?.throwIfAborted();
|
|
5836
6121
|
log?.(signedRequest);
|
|
5837
6122
|
response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
5838
6123
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
@@ -5875,4 +6160,4 @@ function timingSafeEqual(a, b) {
|
|
|
5875
6160
|
return result === 0;
|
|
5876
6161
|
}
|
|
5877
6162
|
//#endregion
|
|
5878
|
-
export {
|
|
6163
|
+
export { instrumentDocumentLoader as $, hasSignature as A, name as At, fetchActorDocument as B, InvalidContextReferenceError as C, hasPortableActor as Ct, createSignature as D, isPortableUri as Dt, compactJsonLd as E, isPortableKeyId as Et, verifyCompactJsonLd as F, validateCryptoKey as G, fetchKeyDetailed as H, verifyJsonLd as I, getVerifiedKeyOwnerEvidence as J, verifyKeyOwnership as K, verifySignature as L, isClearlyMalformedContextReference as M, isInvalidUrlTypeError as N, detachSignature as O, isSameObjectId as Ot, signJsonLd as P, getRemoteHost as Q, wrapContextLoaderForJsonLd as R, normalizeOutgoingActivityJsonLd as S, getRawCanonicalPortableId as St, attachSignature as T, isPortableId as Tt, generateCryptoKeyPair as U, fetchKey as V, importJwk as W, getDurationMs as X, parseKeyOwnerEvidence as Y, getFederationMetrics as Z, verifyObject as _, validateAcceptSignature as _t, signRequest as a, recordCollectionTotalItems as at, verifyPortableObjectProofWithRoot as b, getGatewayKeyBase as bt, verifyRequestDetailed as c, recordInboxActivity as ct, InvalidPortableObjectIdError as d, recordWebFingerHandle as dt, isAbortError$1 as et, classifyFep2277CoreType as f, registerQueueDepthGauge as ft, verifyMapLocalProof as g, parseAcceptSignature as gt, signObject as h, fulfillAcceptSignature as ht, selectRequestSignature as i, recordCollectionRequest as it, hasSignatureLike as j, version as jt, getNormalizationContextLoader as k, isSamePublicKey as kt, fetchPortableGatewayKey as l, recordOutboxActivity as lt, hasProofLike as m, formatAcceptSignature as mt, listRequestSignatures as n, recordCollectionDispatchDuration as nt, validateMaxSignatures as o, recordDocumentCache as ot, createProof as p, preloadedOnlyDocumentLoader as pt, MAX_PORTABLE_KEY_TTL as q, parseRfc9421SignatureInput as r, recordCollectionPageItems as rt, verifyRequest as s, recordFanoutRecipients as st, doubleKnock as t, recordCircuitBreakerStateChange as tt, resolvePortableActorKey as u, recordOutboxEnqueue as ut, verifyPortableObjectProof as v, getAuthenticationOrigin as vt, assertSafeJsonLd as w, isCompatibleKeyId as wt, verifyProof as x, getPortableDid as xt, verifyPortableObjectProofPolicy as y, getCanonicalPortableId as yt, exportJwk as z };
|