@fedify/fedify 2.4.0-dev.2084 → 2.4.0-dev.2099
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-BGlpUocK.mjs → builder-Bno54o_-.mjs} +34 -3
- 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-B6Aj2kGY.mjs → compound-proof-X3SxXVbc.mjs} +191 -13
- package/dist/{context-CZjZYXFC.d.cts → context-C43spTGI.d.cts} +5 -2
- package/dist/{context-BSywYspi.d.ts → context-MORIQJIh.d.ts} +5 -2
- package/dist/{deno-Dn-py9Zy.mjs → deno-dxdvl2XZ.mjs} +1 -1
- package/dist/{docloader-BUq5bYJr.mjs → docloader-eBxQDsk9.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -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/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +7 -7
- 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 +254 -5
- package/dist/federation/portable-gateway-keys.test.mjs +72 -11
- package/dist/federation/portable-inbox.test.mjs +487 -10
- package/dist/federation/portable.test.mjs +182 -4
- 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 +89 -1
- package/dist/{federation-BLGx8JUH.cjs → federation-C4Ef3JKa.cjs} +1487 -1070
- package/dist/{http-B5q1CHtL.cjs → http-C8cZTE3Y.cjs} +1224 -985
- package/dist/{http-4PCCvhsU.js → http-D-rbr_Sy.js} +1663 -1460
- package/dist/{http-B0VRNipE.mjs → http-DsgawTd8.mjs} +82 -7
- package/dist/{key-BJrp524f.mjs → key-DYfmjnmp.mjs} +39 -99
- package/dist/{kv-cache-1KUmAPH7.js → kv-cache-DIxwMeNi.js} +1 -1
- package/dist/{kv-cache-Dhhhrryv.cjs → kv-cache-Dvd-Q-E2.cjs} +1 -1
- package/dist/{kv-cache-C4C3LyYW.mjs → kv-cache-EvOgrFsS.mjs} +1 -1
- package/dist/{ld-BmFz5NLA.mjs → ld-B00wWHzc.mjs} +3 -3
- package/dist/{metrics-Beod6tzO.mjs → metrics-BP3vdBpb.mjs} +1 -1
- package/dist/{middleware-BKeQYPm0.mjs → middleware-Br1_d-Ga.mjs} +606 -389
- package/dist/{middleware-D_9pNYe9.js → middleware-CcXMe400.js} +1602 -1185
- package/dist/{middleware-B7CNsyqt.mjs → middleware-CeBVBa61.mjs} +1 -1
- package/dist/{mod-B2h2KY0q.d.ts → mod-BL5BQHCq.d.ts} +1 -1
- package/dist/{mod-HDSvS-ww.d.cts → mod-C8LieBzF.d.cts} +16 -3
- package/dist/{mod-C9WSpZWm.d.ts → mod-Coacostr.d.ts} +16 -3
- package/dist/{mod-JzkaEXsi.d.cts → mod-DMI4DbOo.d.cts} +1 -1
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +3 -3
- package/dist/mod.d.ts +3 -3
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-CsKVgMzk.mjs → owner-BJPDRDRq.mjs} +5 -4
- package/dist/{owner-BQFZtFV4.cjs → owner-DSzH7wro.cjs} +2 -2
- package/dist/{owner-_pbbgLJK.js → owner-U12fDpQW.js} +2 -2
- package/dist/{portable-key-B4R-CiIV.mjs → portable-key-D0mWbV9G.mjs} +60 -41
- package/dist/portable-key-id-Bva5iT3W.mjs +136 -0
- package/dist/{proof-ozG4HfcQ.mjs → proof-ChjBwnep.mjs} +59 -18
- package/dist/{send-ClPt8nYu.mjs → send-yFX0E-ye.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 +160 -3
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +72 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +3 -3
- package/dist/sig/mod.d.cts +1 -1
- package/dist/sig/mod.d.ts +1 -1
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +69 -5
- package/dist/sig/portable-key.test.mjs +176 -10
- package/dist/sig/proof.test.mjs +107 -4
- package/dist/{sig-k3C8sF7g.cjs → sig-B689Nj7y.cjs} +19 -3
- package/dist/{sig-DhdomAN0.js → sig-C85KqAcx.js} +19 -3
- package/dist/{temporal-Ba0EAZAI.mjs → temporal-Dl9x-Qw4.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +5 -2
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +7 -7
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-
|
|
4
|
+
import { n as version, t as name } from "./deno-dxdvl2XZ.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-BP3vdBpb.mjs";
|
|
6
6
|
import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPVqki78.mjs";
|
|
7
|
-
import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-
|
|
8
|
-
import { n as verifyPortableGatewayKeyDocument } from "./portable-key-
|
|
7
|
+
import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-DYfmjnmp.mjs";
|
|
8
|
+
import { n as verifyPortableGatewayKeyDocument } from "./portable-key-D0mWbV9G.mjs";
|
|
9
9
|
import { getLogger } from "@logtape/logtape";
|
|
10
10
|
import { CryptographicKey } from "@fedify/vocab";
|
|
11
11
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -13,7 +13,7 @@ import { FetchError } from "@fedify/vocab-runtime";
|
|
|
13
13
|
import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
|
|
14
14
|
import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
|
|
15
15
|
import { encodeHex } from "byte-encodings/hex";
|
|
16
|
-
import { Item, decodeDict, encodeItem } from "structured-field-values";
|
|
16
|
+
import { Item, decodeDict, encodeDict, encodeItem } from "structured-field-values";
|
|
17
17
|
//#region src/sig/http.ts
|
|
18
18
|
const DEFAULT_MAX_REDIRECTION = 20;
|
|
19
19
|
const DOUBLE_KNOCK_TRANSPORT_RETRY_DELAY_MS = 100;
|
|
@@ -202,6 +202,81 @@ function parseRfc9421Signature(signature) {
|
|
|
202
202
|
for (const [key, value] of Object.entries(dict)) if (value.value instanceof Uint8Array) result[key] = value.value;
|
|
203
203
|
return result;
|
|
204
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Parses the `Signature` header of draft-cavage HTTP Signatures into its
|
|
207
|
+
* fields, e.g., `keyId` and `headers`.
|
|
208
|
+
*/
|
|
209
|
+
function parseDraftSignature(signature) {
|
|
210
|
+
return Object.fromEntries(signature.split(",").map((pair) => pair.match(/^\s*([A-Za-z]+)=(?:"([^"]*)"|(\d+))\s*$/)).filter((m) => m != null).map((m) => [m[1], m[2] ?? m[3]]));
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Lists the HTTP Signatures that a request claims to carry, without verifying
|
|
214
|
+
* any of them. Signatures whose key IDs are not URLs are left out.
|
|
215
|
+
* @param request The request.
|
|
216
|
+
* @returns The signatures; RFC 9421 ones if the request has
|
|
217
|
+
* a `Signature-Input` header, or a draft-cavage one otherwise.
|
|
218
|
+
* @internal
|
|
219
|
+
*/
|
|
220
|
+
function listRequestSignatures(request) {
|
|
221
|
+
const signatureInput = request.headers.get("Signature-Input");
|
|
222
|
+
if (signatureInput != null) {
|
|
223
|
+
const signatures = [];
|
|
224
|
+
const inputs = parseRfc9421SignatureInput(signatureInput);
|
|
225
|
+
for (const [label, input] of Object.entries(inputs)) {
|
|
226
|
+
const keyId = parseKeyId(input.keyId);
|
|
227
|
+
if (keyId == null) continue;
|
|
228
|
+
signatures.push({
|
|
229
|
+
keyId,
|
|
230
|
+
label,
|
|
231
|
+
components: input.components
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return signatures;
|
|
235
|
+
}
|
|
236
|
+
const signature = request.headers.get("Signature");
|
|
237
|
+
if (signature == null) return [];
|
|
238
|
+
const values = parseDraftSignature(signature);
|
|
239
|
+
const keyId = parseKeyId(values.keyId);
|
|
240
|
+
if (keyId == null) return [];
|
|
241
|
+
return [{
|
|
242
|
+
keyId,
|
|
243
|
+
label: null,
|
|
244
|
+
components: (values.headers ?? "").split(/\s+/g).filter((name) => name !== "").map((name) => ({
|
|
245
|
+
value: name,
|
|
246
|
+
params: {}
|
|
247
|
+
}))
|
|
248
|
+
}];
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Makes a copy of a request that carries only one of its RFC 9421 signatures,
|
|
252
|
+
* so that verifying the copy verifies that signature and no other.
|
|
253
|
+
* A draft-cavage signature is the only one in its request, so the request is
|
|
254
|
+
* just copied.
|
|
255
|
+
* @param request The request, whose body must not be consumed.
|
|
256
|
+
* @param signature One of the signatures that {@link listRequestSignatures}
|
|
257
|
+
* lists for the request.
|
|
258
|
+
* @returns The copy of the request.
|
|
259
|
+
* @internal
|
|
260
|
+
*/
|
|
261
|
+
function selectRequestSignature(request, signature) {
|
|
262
|
+
const copy = request.clone();
|
|
263
|
+
if (signature.label == null) return copy;
|
|
264
|
+
const { label } = signature;
|
|
265
|
+
const pick = (header) => {
|
|
266
|
+
if (header == null) return "";
|
|
267
|
+
let dict;
|
|
268
|
+
try {
|
|
269
|
+
dict = decodeDict(header);
|
|
270
|
+
} catch {
|
|
271
|
+
return "";
|
|
272
|
+
}
|
|
273
|
+
return Object.hasOwn(dict, label) ? encodeDict({ [label]: dict[label] }) : "";
|
|
274
|
+
};
|
|
275
|
+
const headers = new Headers(copy.headers);
|
|
276
|
+
headers.set("Signature-Input", pick(headers.get("Signature-Input")));
|
|
277
|
+
headers.set("Signature", pick(headers.get("Signature")));
|
|
278
|
+
return new Request(copy, { headers });
|
|
279
|
+
}
|
|
205
280
|
async function signRequestRfc9421(request, privateKey, keyId, span, currentTime, bodyBuffer, rfc9421Options) {
|
|
206
281
|
if (privateKey.algorithm.name !== "RSASSA-PKCS1-v1_5") throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
|
|
207
282
|
const url = new URL(request.url);
|
|
@@ -433,7 +508,7 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
|
|
|
433
508
|
logger.debug("Failed to verify; no Signature header found.", { headers: Object.fromEntries(request.headers.entries()) });
|
|
434
509
|
return noSignatureResult();
|
|
435
510
|
}
|
|
436
|
-
const sigValues =
|
|
511
|
+
const sigValues = parseDraftSignature(sigHeader);
|
|
437
512
|
const parsedKeyId = parseKeyId(sigValues.keyId);
|
|
438
513
|
const dateHeader = request.headers.get("Date");
|
|
439
514
|
if (dateHeader == null) {
|
|
@@ -1106,4 +1181,4 @@ function timingSafeEqual(a, b) {
|
|
|
1106
1181
|
return result === 0;
|
|
1107
1182
|
}
|
|
1108
1183
|
//#endregion
|
|
1109
|
-
export {
|
|
1184
|
+
export { listRequestSignatures as a, selectRequestSignature as c, verifyRequest as d, verifyRequestDetailed as f, formatRfc9421SignatureParameters as i, signRequest as l, doubleKnock as n, parseRfc9421Signature as o, formatRfc9421Signature as r, parseRfc9421SignatureInput as s, createRfc9421SignatureBase as t, timingSafeEqual as u };
|
|
@@ -1,105 +1,13 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-
|
|
4
|
+
import { n as version, t as name } from "./deno-dxdvl2XZ.mjs";
|
|
5
|
+
import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-BP3vdBpb.mjs";
|
|
6
|
+
import { a as isCompatibleKeyId, o as isPortableActorDocument, s as isPortableId } from "./portable-key-id-Bva5iT3W.mjs";
|
|
6
7
|
import { getLogger } from "@logtape/logtape";
|
|
7
8
|
import { CryptographicKey, Multikey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
8
9
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
9
|
-
import { FetchError,
|
|
10
|
-
//#region src/sig/portable-key-id.ts
|
|
11
|
-
const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
|
|
12
|
-
/**
|
|
13
|
-
* Checks whether the URL is an `ap:` or `ap+ef61:` URI.
|
|
14
|
-
* @internal
|
|
15
|
-
*/
|
|
16
|
-
function isPortableUri(url) {
|
|
17
|
-
return url.protocol === "ap:" || url.protocol === "ap+ef61:";
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Checks whether an activity is performed by an FEP-ef61 portable actor,
|
|
21
|
-
* i.e., any of its actors has an `ap:` or `ap+ef61:` ID.
|
|
22
|
-
* @internal
|
|
23
|
-
*/
|
|
24
|
-
function hasPortableActor(activity) {
|
|
25
|
-
return activity.actorIds.some(isPortableUri);
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Checks whether a key ID looks like an [FEP-ef61] compatible identifier,
|
|
29
|
-
* i.e., an HTTP(S) URL under a gateway's `/.well-known/apgateway/did:` path.
|
|
30
|
-
* Such a key ID names a key that a gateway holds for a portable actor, so it
|
|
31
|
-
* is resolved only as a gateway key, and never cached in a key cache.
|
|
32
|
-
*
|
|
33
|
-
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
34
|
-
* @internal
|
|
35
|
-
*/
|
|
36
|
-
function isCompatibleKeyId(keyId) {
|
|
37
|
-
return (keyId.protocol === "http:" || keyId.protocol === "https:") && COMPATIBLE_KEY_ID_PATH_PATTERN.test(keyId.pathname);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Gets the canonical portable ID of an actor ID that is either an `ap:` or
|
|
41
|
-
* `ap+ef61:` URI or a compatible identifier.
|
|
42
|
-
* @returns The canonical portable ID, or `null` if the ID is neither, or is
|
|
43
|
-
* malformed.
|
|
44
|
-
* @internal
|
|
45
|
-
*/
|
|
46
|
-
function getCanonicalPortableId(id) {
|
|
47
|
-
try {
|
|
48
|
-
if (isPortableUri(id)) return canonicalizePortableUri(formatIri(id));
|
|
49
|
-
const portable = fromCompatibleEf61Id(id);
|
|
50
|
-
if (portable == null) return null;
|
|
51
|
-
return canonicalizePortableUri(formatIri(portable));
|
|
52
|
-
} catch (error) {
|
|
53
|
-
if (error instanceof TypeError) return null;
|
|
54
|
-
throw error;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Builds the base of the key IDs of a gateway's keys for a portable actor:
|
|
59
|
-
* the actor's compatible identifier on the gateway.
|
|
60
|
-
* @param actorId The portable actor's ID, either an `ap:` or `ap+ef61:` URI
|
|
61
|
-
* or a compatible identifier. It must not have a fragment.
|
|
62
|
-
* @param gateway The gateway's origin, e.g., `https://example.com`.
|
|
63
|
-
* @returns The compatible identifier without query and fragment.
|
|
64
|
-
* @throws {TypeError} If the actor ID is not a portable ID, or has
|
|
65
|
-
* a fragment.
|
|
66
|
-
* @internal
|
|
67
|
-
*/
|
|
68
|
-
function getGatewayKeyBase(actorId, gateway) {
|
|
69
|
-
let portable;
|
|
70
|
-
if (isPortableUri(actorId)) portable = actorId;
|
|
71
|
-
else try {
|
|
72
|
-
portable = fromCompatibleEf61Id(actorId);
|
|
73
|
-
} catch (error) {
|
|
74
|
-
if (!(error instanceof TypeError)) throw error;
|
|
75
|
-
portable = null;
|
|
76
|
-
}
|
|
77
|
-
if (portable == null) throw new TypeError(`The portable actor ID ${actorId.href} must be an ap: or ap+ef61: URI, or an FEP-ef61 compatible identifier.`);
|
|
78
|
-
if (portable.hash !== "") throw new TypeError(`The portable actor ID ${actorId.href} must not have a fragment.`);
|
|
79
|
-
const base = toCompatibleEf61Id(portable, gateway);
|
|
80
|
-
base.search = "";
|
|
81
|
-
base.hash = "";
|
|
82
|
-
return base;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Tells whether a parsed document at a compatible key ID is a portable actor,
|
|
86
|
-
* which makes the key a gateway key that only
|
|
87
|
-
* {@link verifyPortableGatewayKeyDocument} can vouch for.
|
|
88
|
-
* @internal
|
|
89
|
-
*/
|
|
90
|
-
function isPortableActorDocument(object) {
|
|
91
|
-
return isActor(object) && object.id != null && isPortableUri(object.id);
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Compares two public keys by their key material.
|
|
95
|
-
* @internal
|
|
96
|
-
*/
|
|
97
|
-
async function isSamePublicKey(a, b) {
|
|
98
|
-
if (a.algorithm.name !== b.algorithm.name) return false;
|
|
99
|
-
const [x, y] = await Promise.all([crypto.subtle.exportKey("jwk", a), crypto.subtle.exportKey("jwk", b)]);
|
|
100
|
-
return x.kty === y.kty && x.crv === y.crv && x.n === y.n && x.e === y.e && x.x === y.x && x.y === y.y;
|
|
101
|
-
}
|
|
102
|
-
//#endregion
|
|
10
|
+
import { FetchError, getDocumentLoader, parseDidKeyVerificationMethod } from "@fedify/vocab-runtime";
|
|
103
11
|
//#region src/sig/key.ts
|
|
104
12
|
/**
|
|
105
13
|
* Checks if the given key is valid and supported. No-op if the key is valid,
|
|
@@ -200,6 +108,10 @@ async function fetchActorDocument(actorId, options = {}) {
|
|
|
200
108
|
"sig",
|
|
201
109
|
"key"
|
|
202
110
|
]);
|
|
111
|
+
if (isPortableId(actorId)) {
|
|
112
|
+
logger.debug("The actor {actorId} is a portable actor, which is not authenticated by the web origin that serves it.", { actorId: actorId.href });
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
203
115
|
const documentLoader = options.documentLoader ?? getDocumentLoader();
|
|
204
116
|
const contextLoader = options.contextLoader ?? getDocumentLoader();
|
|
205
117
|
const { tracerProvider } = options;
|
|
@@ -233,6 +145,13 @@ async function fetchActorDocument(actorId, options = {}) {
|
|
|
233
145
|
return null;
|
|
234
146
|
}
|
|
235
147
|
if (!isActor(object)) return null;
|
|
148
|
+
if (object.id != null && isPortableId(object.id)) {
|
|
149
|
+
logger.debug("The document served at {documentUrl} claims to be the portable actor {actorId}, which is not authenticated by the web origin that serves it.", {
|
|
150
|
+
documentUrl: documentUrl.href,
|
|
151
|
+
actorId: object.id?.href
|
|
152
|
+
});
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
236
155
|
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
237
156
|
logger.debug("The document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin; refusing to treat it as that actor's document.", {
|
|
238
157
|
documentUrl: documentUrl.href,
|
|
@@ -567,7 +486,7 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
|
|
|
567
486
|
let ownerDocument = null;
|
|
568
487
|
if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object.id == null ? object.clone({ id: cacheKey }) : object;
|
|
569
488
|
else if (isActor(object)) {
|
|
570
|
-
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
489
|
+
if (object.id == null || isPortableId(object.id) || object.id.origin !== documentUrl.origin) {
|
|
571
490
|
logger.debug("Failed to verify; the document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin.", {
|
|
572
491
|
keyId,
|
|
573
492
|
documentUrl: documentUrl.href,
|
|
@@ -643,6 +562,18 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
|
|
|
643
562
|
}
|
|
644
563
|
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
645
564
|
if (!(keyIdBoundByCaller && claimedOwnerId != null && claimedOwnerId.href === stripFragment(cacheKey)?.href)) {
|
|
565
|
+
if (claimedOwnerId != null && isPortableId(claimedOwnerId)) {
|
|
566
|
+
logger.debug("Failed to verify; key {keyId} claims the portable actor {claimedOwnerId} as its owner, but is not its gateway key.", {
|
|
567
|
+
keyId,
|
|
568
|
+
claimedOwnerId: claimedOwnerId.href
|
|
569
|
+
});
|
|
570
|
+
await keyCache?.set(cacheKey, null);
|
|
571
|
+
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
572
|
+
return {
|
|
573
|
+
key: null,
|
|
574
|
+
cached: false
|
|
575
|
+
};
|
|
576
|
+
}
|
|
646
577
|
if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
|
|
647
578
|
else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
|
|
648
579
|
if (await verifyKeyOwnership(key, {
|
|
@@ -672,6 +603,15 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
|
|
|
672
603
|
cached: false
|
|
673
604
|
};
|
|
674
605
|
}
|
|
606
|
+
/**
|
|
607
|
+
* Tells whether a key claims a portable actor as its owner. Such a key can
|
|
608
|
+
* only be a gateway key, which is never cached, so a cached one was cached
|
|
609
|
+
* by an older version that trusted web origins, and is fetched again.
|
|
610
|
+
*/
|
|
611
|
+
function claimsPortableOwner(key) {
|
|
612
|
+
const ownerId = key instanceof CryptographicKey ? key.ownerId : key?.controllerId;
|
|
613
|
+
return ownerId != null && isPortableId(ownerId);
|
|
614
|
+
}
|
|
675
615
|
async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, onFetchError) {
|
|
676
616
|
const start = performance.now();
|
|
677
617
|
let outcome = { result: "error" };
|
|
@@ -698,7 +638,7 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
|
|
|
698
638
|
outcome = { result: "hit" };
|
|
699
639
|
return cachedUnavailable;
|
|
700
640
|
}
|
|
701
|
-
if (cached != null) {
|
|
641
|
+
if (cached != null && !claimsPortableOwner(cached.key)) {
|
|
702
642
|
outcome = { result: "hit" };
|
|
703
643
|
return cached;
|
|
704
644
|
}
|
|
@@ -752,4 +692,4 @@ async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
|
752
692
|
});
|
|
753
693
|
}
|
|
754
694
|
//#endregion
|
|
755
|
-
export { generateCryptoKeyPair as a, verifyKeyOwnership as c,
|
|
695
|
+
export { generateCryptoKeyPair as a, verifyKeyOwnership as c, fetchKeyDetailed as i, fetchActorDocument as n, importJwk as o, fetchKey as r, validateCryptoKey as s, exportJwk as t };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import {
|
|
3
|
+
import { H as validateCryptoKey, et as recordDocumentCache, t as doubleKnock } from "./http-D-rbr_Sy.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
5
|
import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
6
6
|
import { curry } from "es-toolkit";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
2
|
-
const require_http = require("./http-
|
|
2
|
+
const require_http = require("./http-C8cZTE3Y.cjs");
|
|
3
3
|
let _logtape_logtape = require("@logtape/logtape");
|
|
4
4
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
5
5
|
let es_toolkit = require("es-toolkit");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { p as recordDocumentCache } from "./metrics-
|
|
4
|
+
import { p as recordDocumentCache } from "./metrics-BP3vdBpb.mjs";
|
|
5
5
|
import { getLogger } from "@logtape/logtape";
|
|
6
6
|
import { preloadedContexts } from "@fedify/vocab-runtime";
|
|
7
7
|
//#region src/utils/kv-cache.ts
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-
|
|
6
|
-
import { r as fetchKey, s as validateCryptoKey } from "./key-
|
|
4
|
+
import { n as version, t as name } from "./deno-dxdvl2XZ.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-BP3vdBpb.mjs";
|
|
6
|
+
import { r as fetchKey, s as validateCryptoKey } from "./key-DYfmjnmp.mjs";
|
|
7
7
|
import { getLogger } from "@logtape/logtape";
|
|
8
8
|
import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
|
|
9
9
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
4
|
+
import { n as version, t as name } from "./deno-dxdvl2XZ.mjs";
|
|
5
5
|
import { metrics } from "@opentelemetry/api";
|
|
6
6
|
import { FetchError } from "@fedify/vocab-runtime";
|
|
7
7
|
//#region src/federation/metrics.ts
|