@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
|
@@ -16,7 +16,7 @@ json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
|
|
|
16
16
|
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
17
17
|
//#region deno.json
|
|
18
18
|
var name = "@fedify/fedify";
|
|
19
|
-
var version = "2.4.0-dev.
|
|
19
|
+
var version = "2.4.0-dev.2190+be3a8be0";
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/sig/portable-key-id.ts
|
|
22
22
|
const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
|
|
@@ -117,6 +117,50 @@ function getCanonicalPortableId(id) {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
|
+
* Gets the [FEP-fe34] origin of an ID for same-origin checks. The origin of
|
|
121
|
+
* an `ap:` or `ap+ef61:` URI, or of a compatible identifier on any gateway,
|
|
122
|
+
* is the DID of its canonical portable ID, as FEP-ef61 treats a compatible
|
|
123
|
+
* identifier as the portable ID it stands for; unlike `getFe34Origin()`,
|
|
124
|
+
* which gives a compatible identifier the web origin of its gateway. The
|
|
125
|
+
* origin of any other HTTP(S) URL is its web origin, and that of a DID URL
|
|
126
|
+
* is its DID.
|
|
127
|
+
*
|
|
128
|
+
* Only the parsed `URL` is validated, so dot segments that URL parsing has
|
|
129
|
+
* already resolved cannot be detected here.
|
|
130
|
+
*
|
|
131
|
+
* [FEP-fe34]: https://w3id.org/fep/fe34
|
|
132
|
+
* @returns The origin, or `null` if the ID has none that is supported, or is
|
|
133
|
+
* a malformed portable ID or compatible identifier.
|
|
134
|
+
* @internal
|
|
135
|
+
*/
|
|
136
|
+
function getAuthenticationOrigin(id) {
|
|
137
|
+
try {
|
|
138
|
+
if (isPortableId(id)) {
|
|
139
|
+
const canonical = getCanonicalPortableId(id);
|
|
140
|
+
return canonical == null ? null : (0, _fedify_vocab_runtime.getFe34Origin)(canonical);
|
|
141
|
+
}
|
|
142
|
+
return (0, _fedify_vocab_runtime.getFe34Origin)(id);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error instanceof TypeError) return null;
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Checks whether two IDs identify the same object. Two portable IDs, i.e.,
|
|
150
|
+
* `ap:` or `ap+ef61:` URIs or compatible identifiers, are compared by their
|
|
151
|
+
* canonical portable IDs, so that the same object on different gateways and
|
|
152
|
+
* with different location hints matches. Other IDs have to be equal as
|
|
153
|
+
* they are.
|
|
154
|
+
* @internal
|
|
155
|
+
*/
|
|
156
|
+
function isSameObjectId(a, b) {
|
|
157
|
+
const aPortable = isPortableId(a);
|
|
158
|
+
if (aPortable !== isPortableId(b)) return false;
|
|
159
|
+
if (!aPortable) return a.href === b.href;
|
|
160
|
+
const canonical = getCanonicalPortableId(a);
|
|
161
|
+
return canonical != null && canonical === getCanonicalPortableId(b);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
120
164
|
* Gets the canonical portable ID of an `ap:` or `ap+ef61:` URI or
|
|
121
165
|
* a compatible identifier as it is written, before any URL parsing.
|
|
122
166
|
* Parsing an ID into a `URL` resolves dot segments in its path, which would
|
|
@@ -8102,6 +8146,21 @@ async function fetchPortableGatewayKey(keyId, options = {}) {
|
|
|
8102
8146
|
});
|
|
8103
8147
|
}
|
|
8104
8148
|
/**
|
|
8149
|
+
* The maximum number of `@gateway` location hints followed for one `ap:` or
|
|
8150
|
+
* `ap+ef61:` key ID. Whoever made a signature chooses the hints of its key
|
|
8151
|
+
* ID, so fewer of them are followed than when dereferencing objects, which
|
|
8152
|
+
* bounds the gateways one signature can make Fedify ask. Further hints are
|
|
8153
|
+
* ignored.
|
|
8154
|
+
*/
|
|
8155
|
+
const MAX_KEY_ID_GATEWAY_HINTS = 3;
|
|
8156
|
+
/**
|
|
8157
|
+
* The default of {@link PortableActorKeyOptions.lookupTimeout}, in
|
|
8158
|
+
* milliseconds.
|
|
8159
|
+
*/
|
|
8160
|
+
const DEFAULT_KEY_ID_LOOKUP_TIMEOUT = 1e4;
|
|
8161
|
+
/** The longest delay that `setTimeout()` supports, in milliseconds. */
|
|
8162
|
+
const MAX_TIMEOUT = 2 ** 31 - 1;
|
|
8163
|
+
/**
|
|
8105
8164
|
* Resolves a key of a portable actor at an `ap:` or `ap+ef61:` key ID, e.g.,
|
|
8106
8165
|
* `ap://did:key:z6Mk…/actor#main-key`. Such a key ID names no gateway, so
|
|
8107
8166
|
* the actor's document, i.e., the key ID without its fragment, is fetched
|
|
@@ -8129,12 +8188,25 @@ async function fetchPortableGatewayKey(keyId, options = {}) {
|
|
|
8129
8188
|
* unreachable gateway would have served, so the result is an `unavailable`
|
|
8130
8189
|
* that is not cacheable.
|
|
8131
8190
|
*
|
|
8191
|
+
* Only the first {@link MAX_KEY_ID_GATEWAY_HINTS} location hints are
|
|
8192
|
+
* followed, and all the gateways share one timeout, see
|
|
8193
|
+
* {@link PortableActorKeyOptions.lookupTimeout}. A gateway that does not
|
|
8194
|
+
* serve the document in time failed to serve it, like an unreachable one,
|
|
8195
|
+
* with an error that has no HTTP status. A gateway whose document could not
|
|
8196
|
+
* be processed in time, and the gateways left untried, make the result an
|
|
8197
|
+
* `unavailable` that is not cacheable. A result that arrives after
|
|
8198
|
+
* the timeout is ignored, even if it vouches for the key.
|
|
8199
|
+
*
|
|
8132
8200
|
* @param keyId The key ID, which must be an `ap:` or `ap+ef61:` URI.
|
|
8133
|
-
* @param options Loaders and
|
|
8201
|
+
* @param options Loaders, telemetry providers, and the timeout.
|
|
8134
8202
|
* @returns The resolution, which is never `legacy`.
|
|
8203
|
+
* @throws {RangeError} If the timeout is not a non-negative number of
|
|
8204
|
+
* milliseconds that `setTimeout()` supports.
|
|
8135
8205
|
* @internal
|
|
8136
8206
|
*/
|
|
8137
8207
|
async function resolvePortableActorKey(keyId, options = {}) {
|
|
8208
|
+
const lookupTimeout = options.lookupTimeout ?? DEFAULT_KEY_ID_LOOKUP_TIMEOUT;
|
|
8209
|
+
if (!Number.isFinite(lookupTimeout) || lookupTimeout < 0 || lookupTimeout > MAX_TIMEOUT) throw new RangeError(`The lookup timeout must be between 0 and ${MAX_TIMEOUT} ms: ${lookupTimeout}`);
|
|
8138
8210
|
const documentLoader = options.documentLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)();
|
|
8139
8211
|
const contextLoader = options.contextLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)();
|
|
8140
8212
|
let formattedKeyId = keyId.href;
|
|
@@ -8153,75 +8225,67 @@ async function resolvePortableActorKey(keyId, options = {}) {
|
|
|
8153
8225
|
if (keyId.hash === "" || keyId.hash === "#") return reject("The key ID has no fragment; only keys embedded in actor documents are supported.");
|
|
8154
8226
|
const actorUrl = new URL(keyId.href);
|
|
8155
8227
|
actorUrl.hash = "";
|
|
8228
|
+
const gateways = (0, _fedify_vocab_runtime_internal_portable_dereference.getPortableGatewayCandidates)(actorUrl);
|
|
8229
|
+
if (gateways.length > MAX_KEY_ID_GATEWAY_HINTS) logger.debug("Following only the first {max} location hints of key {keyId}.", {
|
|
8230
|
+
keyId: formattedKeyId,
|
|
8231
|
+
max: MAX_KEY_ID_GATEWAY_HINTS
|
|
8232
|
+
});
|
|
8156
8233
|
const requestUrls = [];
|
|
8157
|
-
for (const gateway of (0,
|
|
8234
|
+
for (const gateway of gateways.slice(0, MAX_KEY_ID_GATEWAY_HINTS)) try {
|
|
8158
8235
|
requestUrls.push((0, _fedify_vocab_runtime.toCompatibleEf61Id)(actorUrl, gateway).href);
|
|
8159
8236
|
} catch (error) {
|
|
8160
8237
|
if (!(error instanceof TypeError)) throw error;
|
|
8161
8238
|
return reject(`The key ID cannot be fetched from gateways: ${error}`);
|
|
8162
8239
|
}
|
|
8163
8240
|
if (requestUrls.length < 1) requestUrls.push((0, _fedify_vocab_runtime.formatIri)(actorUrl));
|
|
8241
|
+
const controller = new AbortController();
|
|
8242
|
+
const timeoutError = new DOMException(`Timed out looking up the actor of key ${formattedKeyId} after ${lookupTimeout} ms.`, "TimeoutError");
|
|
8243
|
+
let timer;
|
|
8244
|
+
if (lookupTimeout > 0) timer = setTimeout(() => controller.abort(timeoutError), lookupTimeout);
|
|
8245
|
+
else controller.abort(timeoutError);
|
|
8246
|
+
const { signal } = controller;
|
|
8247
|
+
const attemptOptions = {
|
|
8248
|
+
...options,
|
|
8249
|
+
documentLoader: withDeadline(documentLoader, signal),
|
|
8250
|
+
contextLoader: withDeadline(contextLoader, signal),
|
|
8251
|
+
signal,
|
|
8252
|
+
formattedKeyId
|
|
8253
|
+
};
|
|
8164
8254
|
const attempts = [];
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
} catch (error) {
|
|
8170
|
-
logger.debug("Failed to fetch the actor of key {keyId} from {url}: {error}", {
|
|
8171
|
-
keyId: formattedKeyId,
|
|
8172
|
-
url,
|
|
8173
|
-
error
|
|
8174
|
-
});
|
|
8175
|
-
attempts.push({
|
|
8176
|
-
type: "retrievalFailed",
|
|
8177
|
-
error
|
|
8178
|
-
});
|
|
8179
|
-
continue;
|
|
8180
|
-
}
|
|
8181
|
-
let resolution;
|
|
8182
|
-
try {
|
|
8183
|
-
const object = await _fedify_vocab.Object.fromJsonLd(remoteDocument.document, {
|
|
8184
|
-
documentLoader,
|
|
8185
|
-
contextLoader,
|
|
8186
|
-
tracerProvider: options.tracerProvider
|
|
8187
|
-
});
|
|
8188
|
-
resolution = isPortableActorDocument(object) ? await verifyPortableKeyDocument(remoteDocument.document, object, keyId, false, {
|
|
8189
|
-
...options,
|
|
8190
|
-
documentLoader,
|
|
8191
|
-
contextLoader
|
|
8192
|
-
}) : {
|
|
8193
|
-
type: "rejected",
|
|
8194
|
-
reason: "The document is not a portable actor."
|
|
8195
|
-
};
|
|
8196
|
-
} catch (error) {
|
|
8197
|
-
if (error instanceof TypeError) resolution = {
|
|
8198
|
-
type: "rejected",
|
|
8199
|
-
reason: `The document is malformed: ${error.message}`
|
|
8200
|
-
};
|
|
8201
|
-
else {
|
|
8202
|
-
logger.debug("Failed to process the actor of key {keyId} served from {url}: {error}", {
|
|
8255
|
+
try {
|
|
8256
|
+
for (const url of requestUrls) {
|
|
8257
|
+
if (signal.aborted) {
|
|
8258
|
+
logger.debug("Ran out of time looking up the actor of key {keyId}; gateways from {url} on were not tried.", {
|
|
8203
8259
|
keyId: formattedKeyId,
|
|
8204
|
-
url
|
|
8205
|
-
error
|
|
8260
|
+
url
|
|
8206
8261
|
});
|
|
8207
8262
|
attempts.push({
|
|
8208
8263
|
type: "indeterminate",
|
|
8209
|
-
error
|
|
8264
|
+
error: signal.reason
|
|
8265
|
+
});
|
|
8266
|
+
break;
|
|
8267
|
+
}
|
|
8268
|
+
const state = { processing: false };
|
|
8269
|
+
let attempt;
|
|
8270
|
+
try {
|
|
8271
|
+
attempt = await raceAbort(attemptGateway(url, keyId, attemptOptions, state), signal);
|
|
8272
|
+
} catch (error) {
|
|
8273
|
+
if (!signal.aborted) throw error;
|
|
8274
|
+
logger.debug("Ran out of time looking up the actor of key {keyId} at {url}.", {
|
|
8275
|
+
keyId: formattedKeyId,
|
|
8276
|
+
url
|
|
8277
|
+
});
|
|
8278
|
+
attempts.push({
|
|
8279
|
+
type: state.processing ? "indeterminate" : "retrievalFailed",
|
|
8280
|
+
error: signal.reason
|
|
8210
8281
|
});
|
|
8211
8282
|
continue;
|
|
8212
8283
|
}
|
|
8284
|
+
if (attempt.type === "verified") return attempt;
|
|
8285
|
+
attempts.push(attempt);
|
|
8213
8286
|
}
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
logger.debug("The actor document served from {url} does not vouch for key {keyId}: {reason}", {
|
|
8217
|
-
keyId: formattedKeyId,
|
|
8218
|
-
url,
|
|
8219
|
-
reason
|
|
8220
|
-
});
|
|
8221
|
-
attempts.push({
|
|
8222
|
-
type: "rejected",
|
|
8223
|
-
reason
|
|
8224
|
-
});
|
|
8287
|
+
} finally {
|
|
8288
|
+
clearTimeout(timer);
|
|
8225
8289
|
}
|
|
8226
8290
|
const rejections = attempts.flatMap((a) => a.type === "rejected" ? [a.reason] : []);
|
|
8227
8291
|
if (rejections.length === attempts.length) return reject(rejections.join(" "));
|
|
@@ -8242,6 +8306,139 @@ async function resolvePortableActorKey(keyId, options = {}) {
|
|
|
8242
8306
|
};
|
|
8243
8307
|
}
|
|
8244
8308
|
/**
|
|
8309
|
+
* Fetches the actor's document of a key at an `ap:` or `ap+ef61:` key ID from
|
|
8310
|
+
* one gateway, and tells whether it vouches for the key. It never rejects;
|
|
8311
|
+
* the caller races it against the lookup's timeout, and ignores what it
|
|
8312
|
+
* resolves to after the timeout.
|
|
8313
|
+
*/
|
|
8314
|
+
async function attemptGateway(url, keyId, options, state) {
|
|
8315
|
+
const { documentLoader, contextLoader, signal, formattedKeyId } = options;
|
|
8316
|
+
let remoteDocument;
|
|
8317
|
+
try {
|
|
8318
|
+
remoteDocument = await documentLoader(url);
|
|
8319
|
+
} catch (error) {
|
|
8320
|
+
logger.debug("Failed to fetch the actor of key {keyId} from {url}: {error}", {
|
|
8321
|
+
keyId: formattedKeyId,
|
|
8322
|
+
url,
|
|
8323
|
+
error
|
|
8324
|
+
});
|
|
8325
|
+
return {
|
|
8326
|
+
type: "retrievalFailed",
|
|
8327
|
+
error
|
|
8328
|
+
};
|
|
8329
|
+
}
|
|
8330
|
+
if (signal.aborted) return {
|
|
8331
|
+
type: "indeterminate",
|
|
8332
|
+
error: signal.reason
|
|
8333
|
+
};
|
|
8334
|
+
state.processing = true;
|
|
8335
|
+
let resolution;
|
|
8336
|
+
try {
|
|
8337
|
+
const object = await _fedify_vocab.Object.fromJsonLd(remoteDocument.document, {
|
|
8338
|
+
documentLoader,
|
|
8339
|
+
contextLoader,
|
|
8340
|
+
tracerProvider: options.tracerProvider
|
|
8341
|
+
});
|
|
8342
|
+
signal.throwIfAborted();
|
|
8343
|
+
resolution = isPortableActorDocument(object) ? await verifyPortableKeyDocument(remoteDocument.document, object, keyId, false, options) : {
|
|
8344
|
+
type: "rejected",
|
|
8345
|
+
reason: "The document is not a portable actor."
|
|
8346
|
+
};
|
|
8347
|
+
} catch (error) {
|
|
8348
|
+
if (error instanceof TypeError && !signal.aborted) resolution = {
|
|
8349
|
+
type: "rejected",
|
|
8350
|
+
reason: `The document is malformed: ${error.message}`
|
|
8351
|
+
};
|
|
8352
|
+
else {
|
|
8353
|
+
logger.debug("Failed to process the actor of key {keyId} served from {url}: {error}", {
|
|
8354
|
+
keyId: formattedKeyId,
|
|
8355
|
+
url,
|
|
8356
|
+
error
|
|
8357
|
+
});
|
|
8358
|
+
return {
|
|
8359
|
+
type: "indeterminate",
|
|
8360
|
+
error
|
|
8361
|
+
};
|
|
8362
|
+
}
|
|
8363
|
+
}
|
|
8364
|
+
if (resolution.type === "verified") return resolution;
|
|
8365
|
+
const reason = resolution.type === "rejected" ? resolution.reason : "The document is not a portable actor.";
|
|
8366
|
+
logger.debug("The actor document served from {url} does not vouch for key {keyId}: {reason}", {
|
|
8367
|
+
keyId: formattedKeyId,
|
|
8368
|
+
url,
|
|
8369
|
+
reason
|
|
8370
|
+
});
|
|
8371
|
+
return {
|
|
8372
|
+
type: "rejected",
|
|
8373
|
+
reason
|
|
8374
|
+
};
|
|
8375
|
+
}
|
|
8376
|
+
/**
|
|
8377
|
+
* Wraps a document loader so that it gives up when the given signal aborts,
|
|
8378
|
+
* as well as when the signal in its own options does. The wrapped loader is
|
|
8379
|
+
* passed a signal that aborts in either case, but in case it ignores that
|
|
8380
|
+
* signal, what it resolves to after the abort is ignored, and the wrapper
|
|
8381
|
+
* rejects with the reason of the abort at once. After the abort, the wrapped
|
|
8382
|
+
* loader is not called at all.
|
|
8383
|
+
*/
|
|
8384
|
+
function withDeadline(loader, signal) {
|
|
8385
|
+
return (url, options) => {
|
|
8386
|
+
const combined = combineSignals(signal, options?.signal);
|
|
8387
|
+
return raceAbort(combined.signal.aborted ? Promise.reject(combined.signal.reason) : new Promise((resolve) => resolve(loader(url, {
|
|
8388
|
+
...options,
|
|
8389
|
+
signal: combined.signal
|
|
8390
|
+
}))), combined.signal).finally(combined.dispose);
|
|
8391
|
+
};
|
|
8392
|
+
}
|
|
8393
|
+
/**
|
|
8394
|
+
* Makes a signal that aborts when either of the given signals does, with
|
|
8395
|
+
* the reason of the one that aborted first. `AbortSignal.any()` is not
|
|
8396
|
+
* available on every supported runtime, e.g., Bun before 1.1.4.
|
|
8397
|
+
* @returns The signal, and a function that stops listening to the given
|
|
8398
|
+
* signals.
|
|
8399
|
+
*/
|
|
8400
|
+
function combineSignals(signal, other) {
|
|
8401
|
+
if (other == null || other === signal) return {
|
|
8402
|
+
signal,
|
|
8403
|
+
dispose() {}
|
|
8404
|
+
};
|
|
8405
|
+
const controller = new AbortController();
|
|
8406
|
+
const sources = [signal, other];
|
|
8407
|
+
const dispose = () => {
|
|
8408
|
+
for (const source of sources) source.removeEventListener("abort", onAbort);
|
|
8409
|
+
};
|
|
8410
|
+
function onAbort() {
|
|
8411
|
+
dispose();
|
|
8412
|
+
controller.abort(this.reason);
|
|
8413
|
+
}
|
|
8414
|
+
const aborted = sources.find((source) => source.aborted);
|
|
8415
|
+
if (aborted == null) for (const source of sources) source.addEventListener("abort", onAbort);
|
|
8416
|
+
else controller.abort(aborted.reason);
|
|
8417
|
+
return {
|
|
8418
|
+
signal: controller.signal,
|
|
8419
|
+
dispose
|
|
8420
|
+
};
|
|
8421
|
+
}
|
|
8422
|
+
/**
|
|
8423
|
+
* Waits for the given promise unless the given signal aborts first, in which
|
|
8424
|
+
* case it rejects with the reason of the abort. If the signal has aborted by
|
|
8425
|
+
* the time the promise settles, the abort wins as well, so that nothing that
|
|
8426
|
+
* settles after it counts.
|
|
8427
|
+
*/
|
|
8428
|
+
function raceAbort(promise, signal) {
|
|
8429
|
+
return new Promise((resolve, reject) => {
|
|
8430
|
+
const onAbort = () => reject(signal.reason);
|
|
8431
|
+
const settle = (settled) => {
|
|
8432
|
+
signal.removeEventListener("abort", onAbort);
|
|
8433
|
+
if (signal.aborted) onAbort();
|
|
8434
|
+
else settled();
|
|
8435
|
+
};
|
|
8436
|
+
promise.then((value) => settle(() => resolve(value)), (error) => settle(() => reject(error)));
|
|
8437
|
+
if (signal.aborted) onAbort();
|
|
8438
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
8439
|
+
});
|
|
8440
|
+
}
|
|
8441
|
+
/**
|
|
8245
8442
|
* Finds the nodes that stand for the key with the given canonical portable
|
|
8246
8443
|
* ID. A node whose ID is an `ap:` or `ap+ef61:` URI stands for the key
|
|
8247
8444
|
* itself, e.g., Mitra lists the keys of portable actors that way while
|
|
@@ -8473,16 +8670,19 @@ function parseDraftSignature(signature) {
|
|
|
8473
8670
|
* Lists the HTTP Signatures that a request claims to carry, without verifying
|
|
8474
8671
|
* any of them. Signatures whose key IDs are not URLs are left out.
|
|
8475
8672
|
* @param request The request.
|
|
8673
|
+
* @param maxSignatures The maximum number of RFC 9421 signatures to consider,
|
|
8674
|
+
* counted the same way as by {@link verifyRequest}, so
|
|
8675
|
+
* that the signatures it ignores are not listed either.
|
|
8476
8676
|
* @returns The signatures; RFC 9421 ones if the request has
|
|
8477
8677
|
* a `Signature-Input` header, or a draft-cavage one otherwise.
|
|
8478
8678
|
* @internal
|
|
8479
8679
|
*/
|
|
8480
|
-
function listRequestSignatures(request) {
|
|
8680
|
+
function listRequestSignatures(request, maxSignatures = 3) {
|
|
8481
8681
|
const signatureInput = request.headers.get("Signature-Input");
|
|
8482
8682
|
if (signatureInput != null) {
|
|
8483
8683
|
const signatures = [];
|
|
8484
|
-
const inputs = parseRfc9421SignatureInput(signatureInput);
|
|
8485
|
-
for (const [label, input] of
|
|
8684
|
+
const inputs = Object.entries(parseRfc9421SignatureInput(signatureInput));
|
|
8685
|
+
for (const [label, input] of inputs.slice(0, maxSignatures)) {
|
|
8486
8686
|
const keyId = parseKeyId(input.keyId);
|
|
8487
8687
|
if (keyId == null) continue;
|
|
8488
8688
|
signatures.push({
|
|
@@ -8610,6 +8810,18 @@ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime,
|
|
|
8610
8810
|
body
|
|
8611
8811
|
});
|
|
8612
8812
|
}
|
|
8813
|
+
/**
|
|
8814
|
+
* Checks that a maximum number of signatures to verify is a positive safe
|
|
8815
|
+
* integer or `Infinity`.
|
|
8816
|
+
* @param value The value to check.
|
|
8817
|
+
* @param name The name of the option, used in the error message.
|
|
8818
|
+
* @throws {RangeError} Thrown if the value is not valid.
|
|
8819
|
+
* @internal
|
|
8820
|
+
*/
|
|
8821
|
+
function validateMaxSignatures(value, name) {
|
|
8822
|
+
if (value === Infinity || Number.isSafeInteger(value) && value >= 1) return;
|
|
8823
|
+
throw new RangeError(`${name} must be a positive integer or Infinity.`);
|
|
8824
|
+
}
|
|
8613
8825
|
const supportedHashAlgorithms = {
|
|
8614
8826
|
"sha": "SHA-1",
|
|
8615
8827
|
"sha-256": "SHA-256",
|
|
@@ -8702,6 +8914,8 @@ function recordVerificationResult(span, result) {
|
|
|
8702
8914
|
* @param options Options for verifying the request.
|
|
8703
8915
|
* @returns The public key of the verified signature, or `null` if the signature
|
|
8704
8916
|
* could not be verified.
|
|
8917
|
+
* @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
|
|
8918
|
+
* is not a positive integer or `Infinity`.
|
|
8705
8919
|
*/
|
|
8706
8920
|
async function verifyRequest(request, options = {}) {
|
|
8707
8921
|
const result = await verifyRequestDetailed(request, options);
|
|
@@ -8714,9 +8928,12 @@ async function verifyRequest(request, options = {}) {
|
|
|
8714
8928
|
* @param request The request to verify.
|
|
8715
8929
|
* @param options Options for verifying the request.
|
|
8716
8930
|
* @returns The verified public key, or a structured verification failure.
|
|
8931
|
+
* @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
|
|
8932
|
+
* is not a positive integer or `Infinity`.
|
|
8717
8933
|
* @since 2.1.0
|
|
8718
8934
|
*/
|
|
8719
8935
|
async function verifyRequestDetailed(request, options = {}) {
|
|
8936
|
+
if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
|
|
8720
8937
|
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
|
|
8721
8938
|
if (span.isRecording()) {
|
|
8722
8939
|
span.setAttribute(_opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_METHOD, request.method);
|
|
@@ -9025,7 +9242,100 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
|
|
|
9025
9242
|
}
|
|
9026
9243
|
return false;
|
|
9027
9244
|
}
|
|
9028
|
-
|
|
9245
|
+
/**
|
|
9246
|
+
* Verifies one RFC 9421 signature of a request with a key.
|
|
9247
|
+
* @param request The request, whose body may be consumed already.
|
|
9248
|
+
* @param sigInput The parsed `Signature-Input` member of the signature.
|
|
9249
|
+
* @param sigBytes The bytes of the signature.
|
|
9250
|
+
* @param key The public key named by the signature.
|
|
9251
|
+
* @param span The span to record the algorithm and the signature on.
|
|
9252
|
+
* @returns Whether the signature is verified, and the algorithm of the
|
|
9253
|
+
* signature if it is supported.
|
|
9254
|
+
*/
|
|
9255
|
+
async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
|
|
9256
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
9257
|
+
"fedify",
|
|
9258
|
+
"sig",
|
|
9259
|
+
"http"
|
|
9260
|
+
]);
|
|
9261
|
+
let alg = sigInput.alg?.toLowerCase();
|
|
9262
|
+
if (alg == null) {
|
|
9263
|
+
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";
|
|
9264
|
+
else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
|
|
9265
|
+
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";
|
|
9266
|
+
else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
|
|
9267
|
+
}
|
|
9268
|
+
if (alg) span?.setAttribute("http_signatures.algorithm", alg);
|
|
9269
|
+
const algorithm = alg && rfc9421AlgorithmMap[alg];
|
|
9270
|
+
if (!algorithm) {
|
|
9271
|
+
logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
|
|
9272
|
+
algorithm: sigInput.alg,
|
|
9273
|
+
supported: Object.keys(rfc9421AlgorithmMap)
|
|
9274
|
+
});
|
|
9275
|
+
return {
|
|
9276
|
+
verified: false,
|
|
9277
|
+
mismatched: false
|
|
9278
|
+
};
|
|
9279
|
+
}
|
|
9280
|
+
const metricAlgorithm = alg;
|
|
9281
|
+
let signatureBase;
|
|
9282
|
+
try {
|
|
9283
|
+
signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
|
|
9284
|
+
} catch (error) {
|
|
9285
|
+
logger.debug("Failed to create signature base for verification: {error}", {
|
|
9286
|
+
error,
|
|
9287
|
+
signatureInput: sigInput
|
|
9288
|
+
});
|
|
9289
|
+
return {
|
|
9290
|
+
verified: false,
|
|
9291
|
+
algorithm: metricAlgorithm,
|
|
9292
|
+
mismatched: false
|
|
9293
|
+
};
|
|
9294
|
+
}
|
|
9295
|
+
const signatureBaseBytes = new TextEncoder().encode(signatureBase);
|
|
9296
|
+
span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sigBytes));
|
|
9297
|
+
const algorithmName = typeof algorithm === "string" ? algorithm : algorithm.name;
|
|
9298
|
+
if (key.publicKey.algorithm.name !== algorithmName) {
|
|
9299
|
+
logger.debug("Failed to verify; key {keyId} is not a key for algorithm {algorithm}.", {
|
|
9300
|
+
keyId: sigInput.keyId,
|
|
9301
|
+
algorithm: alg
|
|
9302
|
+
});
|
|
9303
|
+
return {
|
|
9304
|
+
verified: false,
|
|
9305
|
+
algorithm: metricAlgorithm,
|
|
9306
|
+
mismatched: true
|
|
9307
|
+
};
|
|
9308
|
+
}
|
|
9309
|
+
let verified;
|
|
9310
|
+
try {
|
|
9311
|
+
verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
|
|
9312
|
+
} catch (error) {
|
|
9313
|
+
logger.debug("Error during signature verification: {error}", {
|
|
9314
|
+
error,
|
|
9315
|
+
keyId: sigInput.keyId,
|
|
9316
|
+
algorithm: sigInput.alg
|
|
9317
|
+
});
|
|
9318
|
+
return {
|
|
9319
|
+
verified: false,
|
|
9320
|
+
algorithm: metricAlgorithm,
|
|
9321
|
+
mismatched: false
|
|
9322
|
+
};
|
|
9323
|
+
}
|
|
9324
|
+
if (verified) return {
|
|
9325
|
+
verified: true,
|
|
9326
|
+
algorithm: metricAlgorithm
|
|
9327
|
+
};
|
|
9328
|
+
logger.debug("Failed to verify signature with key {keyId}; signature invalid.", {
|
|
9329
|
+
keyId: sigInput.keyId,
|
|
9330
|
+
signatureBase
|
|
9331
|
+
});
|
|
9332
|
+
return {
|
|
9333
|
+
verified: false,
|
|
9334
|
+
algorithm: metricAlgorithm,
|
|
9335
|
+
mismatched: true
|
|
9336
|
+
};
|
|
9337
|
+
}
|
|
9338
|
+
async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, meterProvider, tracerProvider } = {}) {
|
|
9029
9339
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
9030
9340
|
"fedify",
|
|
9031
9341
|
"sig",
|
|
@@ -9038,7 +9348,6 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
9038
9348
|
logger.error("Failed to verify; the request body is locked.", { url: request.url });
|
|
9039
9349
|
return noSignatureResult();
|
|
9040
9350
|
}
|
|
9041
|
-
const originalRequest = request;
|
|
9042
9351
|
request = request.clone();
|
|
9043
9352
|
const signatureInputHeader = request.headers.get("Signature-Input");
|
|
9044
9353
|
if (!signatureInputHeader) {
|
|
@@ -9058,6 +9367,29 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
9058
9367
|
logger.debug("Failed to verify; no valid signatures found in Signature-Input header.", { header: signatureInputHeader });
|
|
9059
9368
|
return invalidSignatureResult(null);
|
|
9060
9369
|
}
|
|
9370
|
+
if (signatureNames.length > maxSignatures) {
|
|
9371
|
+
logger.debug("Verifying only the first {maxSignatures} of the {count} signatures in the Signature-Input header; the rest are ignored.", {
|
|
9372
|
+
maxSignatures,
|
|
9373
|
+
count: signatureNames.length
|
|
9374
|
+
});
|
|
9375
|
+
signatureNames.splice(maxSignatures);
|
|
9376
|
+
}
|
|
9377
|
+
const keyLookups = /* @__PURE__ */ new Map();
|
|
9378
|
+
const refreshedKeyIds = /* @__PURE__ */ new Set();
|
|
9379
|
+
const lookUpKey = (keyId, keyCache) => measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, _fedify_vocab.CryptographicKey, {
|
|
9380
|
+
documentLoader,
|
|
9381
|
+
contextLoader,
|
|
9382
|
+
keyCache,
|
|
9383
|
+
tracerProvider,
|
|
9384
|
+
meterProvider,
|
|
9385
|
+
portableKeyResolvers: createPortableKeyResolvers({
|
|
9386
|
+
documentLoader,
|
|
9387
|
+
contextLoader,
|
|
9388
|
+
keyCache,
|
|
9389
|
+
tracerProvider,
|
|
9390
|
+
meterProvider
|
|
9391
|
+
})
|
|
9392
|
+
}));
|
|
9061
9393
|
let failure = noSignatureResult();
|
|
9062
9394
|
let failureAlgorithm;
|
|
9063
9395
|
const setFailure = (result, algorithm) => {
|
|
@@ -9131,94 +9463,40 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
9131
9463
|
setFailure(invalidSignatureResult(null));
|
|
9132
9464
|
continue;
|
|
9133
9465
|
}
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
tracerProvider,
|
|
9139
|
-
meterProvider,
|
|
9140
|
-
portableKeyResolvers: createPortableKeyResolvers({
|
|
9141
|
-
documentLoader,
|
|
9142
|
-
contextLoader,
|
|
9143
|
-
keyCache,
|
|
9144
|
-
tracerProvider,
|
|
9145
|
-
meterProvider
|
|
9146
|
-
})
|
|
9147
|
-
}));
|
|
9148
|
-
if (fetchError != null) {
|
|
9149
|
-
setFailure(keyFetchErrorResult(keyId, fetchError));
|
|
9150
|
-
continue;
|
|
9151
|
-
}
|
|
9152
|
-
if (!key) {
|
|
9153
|
-
logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
|
|
9154
|
-
setFailure(invalidSignatureResult(keyId));
|
|
9155
|
-
continue;
|
|
9156
|
-
}
|
|
9157
|
-
let alg = sigInput.alg?.toLowerCase();
|
|
9158
|
-
if (alg == null) {
|
|
9159
|
-
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";
|
|
9160
|
-
else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
|
|
9161
|
-
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";
|
|
9162
|
-
else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
|
|
9466
|
+
let lookup = keyLookups.get(keyId.href);
|
|
9467
|
+
if (lookup == null) {
|
|
9468
|
+
lookup = await lookUpKey(keyId, keyCache);
|
|
9469
|
+
keyLookups.set(keyId.href, lookup);
|
|
9163
9470
|
}
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
} catch (error) {
|
|
9179
|
-
logger.debug("Failed to create signature base for verification: {error}", {
|
|
9180
|
-
error,
|
|
9181
|
-
signatureInput: sigInput
|
|
9182
|
-
});
|
|
9183
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
9184
|
-
continue;
|
|
9185
|
-
}
|
|
9186
|
-
const signatureBaseBytes = new TextEncoder().encode(signatureBase);
|
|
9187
|
-
span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sigBytes));
|
|
9188
|
-
try {
|
|
9189
|
-
if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) {
|
|
9190
|
-
metricsContext.algorithm = candidateAlgorithm;
|
|
9471
|
+
while (true) {
|
|
9472
|
+
const { key, cached, fetchError } = lookup;
|
|
9473
|
+
if (fetchError != null) {
|
|
9474
|
+
setFailure(keyFetchErrorResult(keyId, fetchError));
|
|
9475
|
+
break;
|
|
9476
|
+
}
|
|
9477
|
+
if (!key) {
|
|
9478
|
+
logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
|
|
9479
|
+
setFailure(invalidSignatureResult(keyId));
|
|
9480
|
+
break;
|
|
9481
|
+
}
|
|
9482
|
+
const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span);
|
|
9483
|
+
if (result.verified) {
|
|
9484
|
+
metricsContext.algorithm = result.algorithm;
|
|
9191
9485
|
return {
|
|
9192
9486
|
verified: true,
|
|
9193
9487
|
key,
|
|
9194
9488
|
signatureLabel: sigName
|
|
9195
9489
|
};
|
|
9196
|
-
}
|
|
9490
|
+
}
|
|
9491
|
+
if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
|
|
9197
9492
|
logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
|
|
9198
|
-
|
|
9199
|
-
|
|
9200
|
-
|
|
9201
|
-
|
|
9202
|
-
currentTime,
|
|
9203
|
-
keyCache: bypassKeyCacheReads(keyCache),
|
|
9204
|
-
spec: "rfc9421",
|
|
9205
|
-
meterProvider,
|
|
9206
|
-
tracerProvider
|
|
9207
|
-
});
|
|
9208
|
-
} else {
|
|
9209
|
-
logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
|
|
9210
|
-
keyId: sigInput.keyId,
|
|
9211
|
-
signatureBase
|
|
9212
|
-
});
|
|
9213
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
9493
|
+
refreshedKeyIds.add(keyId.href);
|
|
9494
|
+
lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
|
|
9495
|
+
keyLookups.set(keyId.href, lookup);
|
|
9496
|
+
continue;
|
|
9214
9497
|
}
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
error,
|
|
9218
|
-
keyId: sigInput.keyId,
|
|
9219
|
-
algorithm: sigInput.alg
|
|
9220
|
-
});
|
|
9221
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
9498
|
+
setFailure(invalidSignatureResult(keyId), result.algorithm);
|
|
9499
|
+
break;
|
|
9222
9500
|
}
|
|
9223
9501
|
}
|
|
9224
9502
|
metricsContext.algorithm = failureAlgorithm;
|
|
@@ -9318,16 +9596,19 @@ async function doubleKnock(request, identity, options = {}) {
|
|
|
9318
9596
|
}
|
|
9319
9597
|
async function doubleKnockInternal(request, identity, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
9320
9598
|
const { specDeterminer, log, tracerProvider, signal } = options;
|
|
9599
|
+
signal?.throwIfAborted();
|
|
9321
9600
|
const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
9322
9601
|
visited.add(request.url);
|
|
9323
9602
|
const origin = new URL(request.url).origin;
|
|
9324
9603
|
const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
|
|
9604
|
+
signal?.throwIfAborted();
|
|
9325
9605
|
const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
|
|
9326
9606
|
let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
9327
9607
|
spec: firstTrySpec,
|
|
9328
9608
|
tracerProvider,
|
|
9329
9609
|
body
|
|
9330
9610
|
});
|
|
9611
|
+
signal?.throwIfAborted();
|
|
9331
9612
|
log?.(signedRequest);
|
|
9332
9613
|
let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
9333
9614
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
@@ -9352,6 +9633,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
9352
9633
|
let fulfilled = false;
|
|
9353
9634
|
let challengeRequest;
|
|
9354
9635
|
for (const entry of entries) {
|
|
9636
|
+
signal?.throwIfAborted();
|
|
9355
9637
|
const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
|
|
9356
9638
|
if (rfc9421 == null) continue;
|
|
9357
9639
|
logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
|
|
@@ -9374,6 +9656,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
9374
9656
|
}
|
|
9375
9657
|
}
|
|
9376
9658
|
if (fulfilled && challengeRequest != null) {
|
|
9659
|
+
signal?.throwIfAborted();
|
|
9377
9660
|
signedRequest = challengeRequest;
|
|
9378
9661
|
log?.(signedRequest);
|
|
9379
9662
|
response = await fetch(signedRequest, {
|
|
@@ -9398,11 +9681,13 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
9398
9681
|
status: response.status,
|
|
9399
9682
|
statusText: response.statusText
|
|
9400
9683
|
});
|
|
9684
|
+
signal?.throwIfAborted();
|
|
9401
9685
|
signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
9402
9686
|
spec,
|
|
9403
9687
|
tracerProvider,
|
|
9404
9688
|
body
|
|
9405
9689
|
});
|
|
9690
|
+
signal?.throwIfAborted();
|
|
9406
9691
|
log?.(signedRequest);
|
|
9407
9692
|
response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
9408
9693
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
@@ -9565,6 +9850,12 @@ Object.defineProperty(exports, "generateCryptoKeyPair", {
|
|
|
9565
9850
|
return generateCryptoKeyPair;
|
|
9566
9851
|
}
|
|
9567
9852
|
});
|
|
9853
|
+
Object.defineProperty(exports, "getAuthenticationOrigin", {
|
|
9854
|
+
enumerable: true,
|
|
9855
|
+
get: function() {
|
|
9856
|
+
return getAuthenticationOrigin;
|
|
9857
|
+
}
|
|
9858
|
+
});
|
|
9568
9859
|
Object.defineProperty(exports, "getCanonicalPortableId", {
|
|
9569
9860
|
enumerable: true,
|
|
9570
9861
|
get: function() {
|
|
@@ -9697,6 +9988,12 @@ Object.defineProperty(exports, "isPortableUri", {
|
|
|
9697
9988
|
return isPortableUri;
|
|
9698
9989
|
}
|
|
9699
9990
|
});
|
|
9991
|
+
Object.defineProperty(exports, "isSameObjectId", {
|
|
9992
|
+
enumerable: true,
|
|
9993
|
+
get: function() {
|
|
9994
|
+
return isSameObjectId;
|
|
9995
|
+
}
|
|
9996
|
+
});
|
|
9700
9997
|
Object.defineProperty(exports, "isSamePublicKey", {
|
|
9701
9998
|
enumerable: true,
|
|
9702
9999
|
get: function() {
|
|
@@ -9859,6 +10156,12 @@ Object.defineProperty(exports, "validateCryptoKey", {
|
|
|
9859
10156
|
return validateCryptoKey;
|
|
9860
10157
|
}
|
|
9861
10158
|
});
|
|
10159
|
+
Object.defineProperty(exports, "validateMaxSignatures", {
|
|
10160
|
+
enumerable: true,
|
|
10161
|
+
get: function() {
|
|
10162
|
+
return validateMaxSignatures;
|
|
10163
|
+
}
|
|
10164
|
+
});
|
|
9862
10165
|
Object.defineProperty(exports, "verifyCompactJsonLd", {
|
|
9863
10166
|
enumerable: true,
|
|
9864
10167
|
get: function() {
|