@fedify/fedify 2.1.22 → 2.1.24
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/{assert_instance_of-C4Ri6VuN.mjs → assert-BhQd8QHP.mjs} +19 -1
- package/dist/{builder-uOFBJkK3.mjs → builder-BatYe9a2.mjs} +3 -3
- package/dist/{client-CZWLyLmp.mjs → client-DNiBssfx.mjs} +78 -4
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +3 -4
- package/dist/{context-_l9t359k.d.ts → context-BgU5UWTx.d.ts} +4 -3
- package/dist/{context-o22vjzS2.d.cts → context-D4s0Wk3N.d.cts} +4 -3
- package/dist/{deno-Dq7ws4iI.mjs → deno-BXkm_rws.mjs} +1 -1
- package/dist/{docloader-BC4p0nXc.mjs → docloader-DdEK2tXP.mjs} +2 -2
- package/dist/federation/builder.test.mjs +2 -2
- package/dist/federation/collection.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +127 -9
- package/dist/federation/idempotency.test.mjs +4 -4
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/keycache.test.mjs +44 -10
- package/dist/federation/kv.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +99 -11
- 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/mq.test.mjs +1 -1
- package/dist/federation/negotiation.test.mjs +2 -2
- package/dist/federation/retry.test.mjs +1 -1
- package/dist/federation/router.test.mjs +1 -1
- package/dist/federation/send.test.mjs +138 -6
- package/dist/federation/temporal.test.mjs +2 -2
- package/dist/federation/webfinger.test.mjs +2 -2
- package/dist/{http-CWMbnDWz.cjs → http-7FmLhLDv.cjs} +166 -6
- package/dist/{http-C5HQIcH6.js → http-Chr54tTL.js} +155 -7
- package/dist/{http-BDZeS5om.d.ts → http-Dl00HXr4.d.ts} +1 -1
- package/dist/{http-C87EWkO0.d.cts → http-DqZhyEso.d.cts} +1 -1
- package/dist/{http-DFOoxt_R.mjs → http-UyebnM2c.mjs} +3 -3
- package/dist/{inbox-B07FmpqX.mjs → inbox-ByQ2iD14.mjs} +1 -1
- package/dist/{key-BhxG-x5D.mjs → key-A8SuFznD.mjs} +154 -6
- package/dist/{keycache-CCSwkQcY.mjs → keycache-CaO7wyQA.mjs} +12 -4
- package/dist/{kv-cache-ByRYF-t6.cjs → kv-cache-DJhBDA9T.cjs} +1 -1
- package/dist/{kv-cache-C4CXZOaq.js → kv-cache-J6hmiAEd.js} +1 -1
- package/dist/{ld-CHqytT_K.mjs → ld-BSvHU-S0.mjs} +2 -2
- package/dist/{middleware-B1736exe.cjs → middleware-Bddj287b.cjs} +1 -1
- package/dist/{middleware-_PzelxB9.cjs → middleware-CCYqHlzn.cjs} +90 -14
- package/dist/{middleware-Dyz-bNVJ.js → middleware-Do50b12a.js} +90 -14
- package/dist/{middleware-DRMVB2Bf.mjs → middleware-DvxNTKpl.mjs} +1 -1
- package/dist/{middleware-CFbSzb-d.mjs → middleware-Dy18K1xP.mjs} +31 -20
- package/dist/{mod-CjiK2zrW.d.ts → mod-BLDHhuW0.d.ts} +1 -1
- package/dist/{mod-0dF0OeGw.d.cts → mod-Bx80LUTA.d.cts} +1 -1
- package/dist/{mod-BTWFtZwa.d.ts → mod-CMK_S-eX.d.ts} +1 -1
- package/dist/{mod-Bv1elC-b.d.ts → mod-DcAiY74j.d.ts} +1 -1
- package/dist/{mod-D5YlzzP3.d.cts → mod-DiuFTYUt.d.cts} +1 -1
- package/dist/{mod-dxOnKp2Y.d.cts → mod-DniKVJ_C.d.cts} +1 -1
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +28 -1
- package/dist/nodeinfo/handler.test.mjs +2 -2
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/nodeinfo/types.test.mjs +1 -1
- package/dist/otel/exporter.test.mjs +1 -1
- package/dist/{owner-DEvZuyOE.d.cts → owner-B3hNQCG8.d.cts} +11 -0
- package/dist/owner-CaaCVHsG.mjs +175 -0
- package/dist/{owner-CnngXDNJ.d.ts → owner-CiBPcC6F.d.ts} +11 -0
- package/dist/{proof-DtJrGFAd.js → proof-BrOQrPQf.js} +112 -58
- package/dist/{proof-BLCy90_K.cjs → proof-CaCUMcvk.cjs} +112 -58
- package/dist/{proof-B9xBlvFD.mjs → proof-_AyA4r7z.mjs} +2 -2
- package/dist/{send-TxZWfiOg.mjs → send-B1CRgb14.mjs} +64 -6
- package/dist/sig/accept.test.mjs +1 -1
- package/dist/sig/http.test.mjs +4 -4
- package/dist/sig/key.test.mjs +170 -3
- package/dist/sig/ld.test.mjs +56 -4
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +3 -3
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +105 -6
- package/dist/sig/proof.test.mjs +72 -3
- package/dist/{temporal-Q6m8vHyx.mjs → temporal-Pmk5ouB1.mjs} +1 -1
- package/dist/{types-m8u9L86X.cjs → types-DWs-qZaY.cjs} +94 -2
- package/dist/{types-DTONwNqL.js → types-sXITXcWa.js} +78 -4
- package/dist/utils/docloader.test.mjs +28 -4
- 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 +5 -5
- package/dist/assert-ddO5KLpe.mjs +0 -23
- package/dist/owner-D79Nd_56.mjs +0 -120
- /package/dist/{accept-Dd__NiUL.mjs → accept-BavzedPB.mjs} +0 -0
- /package/dist/{collection-BD6-SZ6O.mjs → collection-ChgDTHLz.mjs} +0 -0
- /package/dist/{keys-BAK-tUlf.mjs → keys-CtZLJq76.mjs} +0 -0
- /package/dist/{kv-tL2TOE9X.mjs → kv-BrZHNugx.mjs} +0 -0
- /package/dist/{kv-cache-FsT1ljhs.mjs → kv-cache-DqEKe5E0.mjs} +0 -0
- /package/dist/{negotiation-DnsfFF8I.mjs → negotiation-BehA2uul.mjs} +0 -0
- /package/dist/{retry-CJL0poaU.mjs → retry-B_E3V_Dx.mjs} +0 -0
- /package/dist/{types-DCP0WLdt.mjs → types-C37hquWI.mjs} +0 -0
|
@@ -4,13 +4,13 @@ import { getLogger } from "@logtape/logtape";
|
|
|
4
4
|
import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
5
5
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
6
6
|
import { encodeHex } from "byte-encodings/hex";
|
|
7
|
-
import { Item, decodeDict, encodeDict, encodeItem } from "structured-field-values";
|
|
8
7
|
import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
8
|
+
import { Item, decodeDict, encodeDict, encodeItem } from "structured-field-values";
|
|
9
9
|
import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
|
|
10
10
|
import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
|
|
11
11
|
//#region deno.json
|
|
12
12
|
var name = "@fedify/fedify";
|
|
13
|
-
var version = "2.1.
|
|
13
|
+
var version = "2.1.24";
|
|
14
14
|
//#endregion
|
|
15
15
|
//#region src/sig/accept.ts
|
|
16
16
|
/**
|
|
@@ -230,6 +230,118 @@ async function importJwk(jwk, type) {
|
|
|
230
230
|
validateCryptoKey(key, type);
|
|
231
231
|
return key;
|
|
232
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Fetches the document that the given actor URI dereferences to, and returns
|
|
235
|
+
* the actor only if the host that served the document is authoritative for
|
|
236
|
+
* the id the document claims.
|
|
237
|
+
*
|
|
238
|
+
* Only the origin that serves an actor id can speak for it. Without that
|
|
239
|
+
* rule any host could serve a document describing somebody else's actor—and
|
|
240
|
+
* listing its own keys as that actor's.
|
|
241
|
+
*
|
|
242
|
+
* @param actorId The URI of the actor to fetch.
|
|
243
|
+
* @param options Options for fetching the document.
|
|
244
|
+
* @returns The actor, or `null` if the document cannot be fetched, is not an
|
|
245
|
+
* actor, or belongs to another origin.
|
|
246
|
+
* @internal
|
|
247
|
+
*/
|
|
248
|
+
async function fetchActorDocument(actorId, options = {}) {
|
|
249
|
+
const logger = getLogger([
|
|
250
|
+
"fedify",
|
|
251
|
+
"sig",
|
|
252
|
+
"key"
|
|
253
|
+
]);
|
|
254
|
+
const documentLoader = options.documentLoader ?? getDocumentLoader();
|
|
255
|
+
const contextLoader = options.contextLoader ?? getDocumentLoader();
|
|
256
|
+
const { tracerProvider } = options;
|
|
257
|
+
let document;
|
|
258
|
+
let documentUrl = actorId;
|
|
259
|
+
try {
|
|
260
|
+
const remoteDocument = await documentLoader(actorId.href);
|
|
261
|
+
document = remoteDocument.document;
|
|
262
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", actorId);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
logger.debug("Failed to fetch the actor {actorId}: {error}", {
|
|
265
|
+
actorId: actorId.href,
|
|
266
|
+
error
|
|
267
|
+
});
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
let object;
|
|
271
|
+
try {
|
|
272
|
+
object = await Object$1.fromJsonLd(document, {
|
|
273
|
+
documentLoader,
|
|
274
|
+
contextLoader,
|
|
275
|
+
tracerProvider,
|
|
276
|
+
baseUrl: documentUrl
|
|
277
|
+
});
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (!(error instanceof TypeError)) throw error;
|
|
280
|
+
logger.debug("The document served at {documentUrl} is not a valid object: {error}", {
|
|
281
|
+
documentUrl: documentUrl.href,
|
|
282
|
+
error
|
|
283
|
+
});
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
if (!isActor(object)) return null;
|
|
287
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
288
|
+
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.", {
|
|
289
|
+
documentUrl: documentUrl.href,
|
|
290
|
+
actorId: object.id?.href
|
|
291
|
+
});
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
return object;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Resolves the owner that the given key claims, and returns it only if the
|
|
298
|
+
* claim holds.
|
|
299
|
+
*
|
|
300
|
+
* The `owner` of a {@link CryptographicKey}, like the `controller` of
|
|
301
|
+
* a {@link Multikey}, proves nothing on its own: the key document and the
|
|
302
|
+
* claim inside it are served by the same host, so anyone able to serve a key
|
|
303
|
+
* document can name any actor in the world as its owner. The claim becomes
|
|
304
|
+
* meaningful only once the named actor's own document is fetched and turns
|
|
305
|
+
* out to link back to the key. This function performs that mutual-link
|
|
306
|
+
* check, which is the only sound way to learn a fetched key's owner.
|
|
307
|
+
*
|
|
308
|
+
* The claimed owner is always dereferenced, never read out of the key
|
|
309
|
+
* document: an owner embedded there is written by whoever wrote the key.
|
|
310
|
+
*
|
|
311
|
+
* @param key The key whose ownership claim is to be verified. It must carry
|
|
312
|
+
* an `id`, as that is what the owner has to link back to.
|
|
313
|
+
* @param options Options for fetching the claimed owner.
|
|
314
|
+
* @returns The verified owner, or `null` if the key claims no owner, the
|
|
315
|
+
* owner cannot be fetched, or the owner does not link back to
|
|
316
|
+
* the key.
|
|
317
|
+
* @internal
|
|
318
|
+
*/
|
|
319
|
+
async function verifyKeyOwnership(key, options = {}) {
|
|
320
|
+
const logger = getLogger([
|
|
321
|
+
"fedify",
|
|
322
|
+
"sig",
|
|
323
|
+
"key"
|
|
324
|
+
]);
|
|
325
|
+
const keyId = key.id;
|
|
326
|
+
if (keyId == null) return null;
|
|
327
|
+
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
328
|
+
if (claimedOwnerId == null) return null;
|
|
329
|
+
const owner = await fetchActorDocument(claimedOwnerId, options);
|
|
330
|
+
if (owner == null) {
|
|
331
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
|
|
332
|
+
keyId: keyId.href,
|
|
333
|
+
claimedOwnerId: claimedOwnerId.href
|
|
334
|
+
});
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
const linkedKeyIds = key instanceof CryptographicKey ? owner.publicKeyIds : owner.assertionMethodIds;
|
|
338
|
+
for (const linkedKeyId of linkedKeyIds) if (linkedKeyId.href === keyId.href) return owner;
|
|
339
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims does not list the key as its own.", {
|
|
340
|
+
keyId: keyId.href,
|
|
341
|
+
claimedOwnerId: claimedOwnerId.href
|
|
342
|
+
});
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
233
345
|
async function withFetchKeySpan(keyId, tracerProvider, fetcher) {
|
|
234
346
|
tracerProvider ??= trace.getTracerProvider();
|
|
235
347
|
return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.fetch_key", {
|
|
@@ -357,7 +469,7 @@ async function getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger) {
|
|
|
357
469
|
async function clearFetchErrorMetadata(keyId, keyCache) {
|
|
358
470
|
await keyCache?.setFetchError?.(keyId, null);
|
|
359
471
|
}
|
|
360
|
-
async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
|
|
472
|
+
async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
|
|
361
473
|
let object;
|
|
362
474
|
try {
|
|
363
475
|
object = await Object$1.fromJsonLd(document, {
|
|
@@ -387,8 +499,22 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
387
499
|
}
|
|
388
500
|
}
|
|
389
501
|
let key = null;
|
|
390
|
-
|
|
502
|
+
let ownerDocument = null;
|
|
503
|
+
if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object.id == null ? object.clone({ id: cacheKey }) : object;
|
|
391
504
|
else if (isActor(object)) {
|
|
505
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
506
|
+
logger.debug("Failed to verify; the document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin.", {
|
|
507
|
+
keyId,
|
|
508
|
+
documentUrl: documentUrl.href,
|
|
509
|
+
actorId: object.id?.href
|
|
510
|
+
});
|
|
511
|
+
await keyCache?.set(cacheKey, null);
|
|
512
|
+
return {
|
|
513
|
+
key: null,
|
|
514
|
+
cached: false
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
ownerDocument = object;
|
|
392
518
|
const keys = cls === CryptographicKey ? object.getPublicKeys({
|
|
393
519
|
documentLoader,
|
|
394
520
|
contextLoader,
|
|
@@ -450,6 +576,25 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
450
576
|
cached: false
|
|
451
577
|
};
|
|
452
578
|
}
|
|
579
|
+
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
580
|
+
if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
|
|
581
|
+
else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
|
|
582
|
+
if (await verifyKeyOwnership(key, {
|
|
583
|
+
documentLoader,
|
|
584
|
+
contextLoader,
|
|
585
|
+
tracerProvider
|
|
586
|
+
}) == null) {
|
|
587
|
+
logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
|
|
588
|
+
keyId,
|
|
589
|
+
claimedOwnerId: claimedOwnerId.href
|
|
590
|
+
});
|
|
591
|
+
await keyCache?.set(cacheKey, null);
|
|
592
|
+
return {
|
|
593
|
+
key: null,
|
|
594
|
+
cached: false
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
}
|
|
453
598
|
if (keyCache != null) {
|
|
454
599
|
await keyCache.set(cacheKey, key);
|
|
455
600
|
logger.debug("Key {keyId} cached.", { keyId });
|
|
@@ -473,12 +618,15 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
|
|
|
473
618
|
if (cached != null) return cached;
|
|
474
619
|
logger.debug("Fetching key {keyId} to verify signature...", { keyId });
|
|
475
620
|
let document;
|
|
621
|
+
let documentUrl = cacheKey;
|
|
476
622
|
try {
|
|
477
|
-
|
|
623
|
+
const remoteDocument = await (options.documentLoader ?? getDocumentLoader())(keyId);
|
|
624
|
+
document = remoteDocument.document;
|
|
625
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", cacheKey);
|
|
478
626
|
} catch (error) {
|
|
479
627
|
return await onFetchError(error, cacheKey, keyId, keyCache, logger);
|
|
480
628
|
}
|
|
481
|
-
return await resolveFetchedKey(document, cacheKey, keyId, cls, options, logger);
|
|
629
|
+
return await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
|
|
482
630
|
}
|
|
483
631
|
async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
484
632
|
return await fetchKeyWithResult(typeof keyId === "string" ? new URL(keyId) : keyId, cls, options, (_cacheKey, _keyId, _keyCache, _logger) => {
|
|
@@ -1531,4 +1679,4 @@ function timingSafeEqual(a, b) {
|
|
|
1531
1679
|
return result === 0;
|
|
1532
1680
|
}
|
|
1533
1681
|
//#endregion
|
|
1534
|
-
export {
|
|
1682
|
+
export { validateAcceptSignature as _, verifyRequestDetailed as a, fetchKey as c, importJwk as d, validateCryptoKey as f, parseAcceptSignature as g, fulfillAcceptSignature as h, verifyRequest as i, fetchKeyDetailed as l, formatAcceptSignature as m, parseRfc9421SignatureInput as n, exportJwk as o, verifyKeyOwnership as p, signRequest as r, fetchActorDocument as s, doubleKnock as t, generateCryptoKeyPair as u, name as v, version as y };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
|
-
import { CryptographicKey, Multikey } from "@fedify/vocab";
|
|
2
|
+
import { Actor, CryptographicKey, Multikey } from "@fedify/vocab";
|
|
3
3
|
import { TracerProvider } from "@opentelemetry/api";
|
|
4
4
|
import { DocumentLoader } from "@fedify/vocab-runtime";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
|
-
import { CryptographicKey, Multikey } from "@fedify/vocab";
|
|
2
|
+
import { Actor, CryptographicKey, Multikey } from "@fedify/vocab";
|
|
3
3
|
import { DocumentLoader } from "@fedify/vocab-runtime";
|
|
4
4
|
import { TracerProvider } from "@opentelemetry/api";
|
|
5
5
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-
|
|
6
|
-
import {
|
|
4
|
+
import { n as version, t as name } from "./deno-BXkm_rws.mjs";
|
|
5
|
+
import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-BavzedPB.mjs";
|
|
6
|
+
import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-A8SuFznD.mjs";
|
|
7
7
|
import { CryptographicKey } from "@fedify/vocab";
|
|
8
8
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
9
9
|
import { FetchError } from "@fedify/vocab-runtime";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Temporal } from "@js-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-BXkm_rws.mjs";
|
|
5
5
|
import { Activity, getTypeId } from "@fedify/vocab";
|
|
6
6
|
import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
|
|
7
7
|
import { getLogger } from "@logtape/logtape";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "@js-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-BXkm_rws.mjs";
|
|
5
5
|
import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
6
6
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
7
7
|
import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
@@ -85,6 +85,118 @@ async function importJwk(jwk, type) {
|
|
|
85
85
|
validateCryptoKey(key, type);
|
|
86
86
|
return key;
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Fetches the document that the given actor URI dereferences to, and returns
|
|
90
|
+
* the actor only if the host that served the document is authoritative for
|
|
91
|
+
* the id the document claims.
|
|
92
|
+
*
|
|
93
|
+
* Only the origin that serves an actor id can speak for it. Without that
|
|
94
|
+
* rule any host could serve a document describing somebody else's actor—and
|
|
95
|
+
* listing its own keys as that actor's.
|
|
96
|
+
*
|
|
97
|
+
* @param actorId The URI of the actor to fetch.
|
|
98
|
+
* @param options Options for fetching the document.
|
|
99
|
+
* @returns The actor, or `null` if the document cannot be fetched, is not an
|
|
100
|
+
* actor, or belongs to another origin.
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
async function fetchActorDocument(actorId, options = {}) {
|
|
104
|
+
const logger = getLogger([
|
|
105
|
+
"fedify",
|
|
106
|
+
"sig",
|
|
107
|
+
"key"
|
|
108
|
+
]);
|
|
109
|
+
const documentLoader = options.documentLoader ?? getDocumentLoader();
|
|
110
|
+
const contextLoader = options.contextLoader ?? getDocumentLoader();
|
|
111
|
+
const { tracerProvider } = options;
|
|
112
|
+
let document;
|
|
113
|
+
let documentUrl = actorId;
|
|
114
|
+
try {
|
|
115
|
+
const remoteDocument = await documentLoader(actorId.href);
|
|
116
|
+
document = remoteDocument.document;
|
|
117
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", actorId);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
logger.debug("Failed to fetch the actor {actorId}: {error}", {
|
|
120
|
+
actorId: actorId.href,
|
|
121
|
+
error
|
|
122
|
+
});
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
let object;
|
|
126
|
+
try {
|
|
127
|
+
object = await Object$1.fromJsonLd(document, {
|
|
128
|
+
documentLoader,
|
|
129
|
+
contextLoader,
|
|
130
|
+
tracerProvider,
|
|
131
|
+
baseUrl: documentUrl
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (!(error instanceof TypeError)) throw error;
|
|
135
|
+
logger.debug("The document served at {documentUrl} is not a valid object: {error}", {
|
|
136
|
+
documentUrl: documentUrl.href,
|
|
137
|
+
error
|
|
138
|
+
});
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
if (!isActor(object)) return null;
|
|
142
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
143
|
+
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.", {
|
|
144
|
+
documentUrl: documentUrl.href,
|
|
145
|
+
actorId: object.id?.href
|
|
146
|
+
});
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
return object;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Resolves the owner that the given key claims, and returns it only if the
|
|
153
|
+
* claim holds.
|
|
154
|
+
*
|
|
155
|
+
* The `owner` of a {@link CryptographicKey}, like the `controller` of
|
|
156
|
+
* a {@link Multikey}, proves nothing on its own: the key document and the
|
|
157
|
+
* claim inside it are served by the same host, so anyone able to serve a key
|
|
158
|
+
* document can name any actor in the world as its owner. The claim becomes
|
|
159
|
+
* meaningful only once the named actor's own document is fetched and turns
|
|
160
|
+
* out to link back to the key. This function performs that mutual-link
|
|
161
|
+
* check, which is the only sound way to learn a fetched key's owner.
|
|
162
|
+
*
|
|
163
|
+
* The claimed owner is always dereferenced, never read out of the key
|
|
164
|
+
* document: an owner embedded there is written by whoever wrote the key.
|
|
165
|
+
*
|
|
166
|
+
* @param key The key whose ownership claim is to be verified. It must carry
|
|
167
|
+
* an `id`, as that is what the owner has to link back to.
|
|
168
|
+
* @param options Options for fetching the claimed owner.
|
|
169
|
+
* @returns The verified owner, or `null` if the key claims no owner, the
|
|
170
|
+
* owner cannot be fetched, or the owner does not link back to
|
|
171
|
+
* the key.
|
|
172
|
+
* @internal
|
|
173
|
+
*/
|
|
174
|
+
async function verifyKeyOwnership(key, options = {}) {
|
|
175
|
+
const logger = getLogger([
|
|
176
|
+
"fedify",
|
|
177
|
+
"sig",
|
|
178
|
+
"key"
|
|
179
|
+
]);
|
|
180
|
+
const keyId = key.id;
|
|
181
|
+
if (keyId == null) return null;
|
|
182
|
+
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
183
|
+
if (claimedOwnerId == null) return null;
|
|
184
|
+
const owner = await fetchActorDocument(claimedOwnerId, options);
|
|
185
|
+
if (owner == null) {
|
|
186
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
|
|
187
|
+
keyId: keyId.href,
|
|
188
|
+
claimedOwnerId: claimedOwnerId.href
|
|
189
|
+
});
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const linkedKeyIds = key instanceof CryptographicKey ? owner.publicKeyIds : owner.assertionMethodIds;
|
|
193
|
+
for (const linkedKeyId of linkedKeyIds) if (linkedKeyId.href === keyId.href) return owner;
|
|
194
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims does not list the key as its own.", {
|
|
195
|
+
keyId: keyId.href,
|
|
196
|
+
claimedOwnerId: claimedOwnerId.href
|
|
197
|
+
});
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
88
200
|
async function withFetchKeySpan(keyId, tracerProvider, fetcher) {
|
|
89
201
|
tracerProvider ??= trace.getTracerProvider();
|
|
90
202
|
return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.fetch_key", {
|
|
@@ -212,7 +324,7 @@ async function getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger) {
|
|
|
212
324
|
async function clearFetchErrorMetadata(keyId, keyCache) {
|
|
213
325
|
await keyCache?.setFetchError?.(keyId, null);
|
|
214
326
|
}
|
|
215
|
-
async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
|
|
327
|
+
async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
|
|
216
328
|
let object;
|
|
217
329
|
try {
|
|
218
330
|
object = await Object$1.fromJsonLd(document, {
|
|
@@ -242,8 +354,22 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
242
354
|
}
|
|
243
355
|
}
|
|
244
356
|
let key = null;
|
|
245
|
-
|
|
357
|
+
let ownerDocument = null;
|
|
358
|
+
if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object.id == null ? object.clone({ id: cacheKey }) : object;
|
|
246
359
|
else if (isActor(object)) {
|
|
360
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
361
|
+
logger.debug("Failed to verify; the document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin.", {
|
|
362
|
+
keyId,
|
|
363
|
+
documentUrl: documentUrl.href,
|
|
364
|
+
actorId: object.id?.href
|
|
365
|
+
});
|
|
366
|
+
await keyCache?.set(cacheKey, null);
|
|
367
|
+
return {
|
|
368
|
+
key: null,
|
|
369
|
+
cached: false
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
ownerDocument = object;
|
|
247
373
|
const keys = cls === CryptographicKey ? object.getPublicKeys({
|
|
248
374
|
documentLoader,
|
|
249
375
|
contextLoader,
|
|
@@ -305,6 +431,25 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
305
431
|
cached: false
|
|
306
432
|
};
|
|
307
433
|
}
|
|
434
|
+
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
435
|
+
if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
|
|
436
|
+
else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
|
|
437
|
+
if (await verifyKeyOwnership(key, {
|
|
438
|
+
documentLoader,
|
|
439
|
+
contextLoader,
|
|
440
|
+
tracerProvider
|
|
441
|
+
}) == null) {
|
|
442
|
+
logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
|
|
443
|
+
keyId,
|
|
444
|
+
claimedOwnerId: claimedOwnerId.href
|
|
445
|
+
});
|
|
446
|
+
await keyCache?.set(cacheKey, null);
|
|
447
|
+
return {
|
|
448
|
+
key: null,
|
|
449
|
+
cached: false
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
}
|
|
308
453
|
if (keyCache != null) {
|
|
309
454
|
await keyCache.set(cacheKey, key);
|
|
310
455
|
logger.debug("Key {keyId} cached.", { keyId });
|
|
@@ -328,12 +473,15 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
|
|
|
328
473
|
if (cached != null) return cached;
|
|
329
474
|
logger.debug("Fetching key {keyId} to verify signature...", { keyId });
|
|
330
475
|
let document;
|
|
476
|
+
let documentUrl = cacheKey;
|
|
331
477
|
try {
|
|
332
|
-
|
|
478
|
+
const remoteDocument = await (options.documentLoader ?? getDocumentLoader())(keyId);
|
|
479
|
+
document = remoteDocument.document;
|
|
480
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", cacheKey);
|
|
333
481
|
} catch (error) {
|
|
334
482
|
return await onFetchError(error, cacheKey, keyId, keyCache, logger);
|
|
335
483
|
}
|
|
336
|
-
return await resolveFetchedKey(document, cacheKey, keyId, cls, options, logger);
|
|
484
|
+
return await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
|
|
337
485
|
}
|
|
338
486
|
async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
339
487
|
return await fetchKeyWithResult(typeof keyId === "string" ? new URL(keyId) : keyId, cls, options, (_cacheKey, _keyId, _keyCache, _logger) => {
|
|
@@ -359,4 +507,4 @@ async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
|
359
507
|
});
|
|
360
508
|
}
|
|
361
509
|
//#endregion
|
|
362
|
-
export {
|
|
510
|
+
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 };
|
|
@@ -3,6 +3,7 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { CryptographicKey, Multikey } from "@fedify/vocab";
|
|
5
5
|
//#region src/federation/keycache.ts
|
|
6
|
+
const KEY_CACHE_GENERATION = "2";
|
|
6
7
|
var KvKeyCache = class {
|
|
7
8
|
kv;
|
|
8
9
|
prefix;
|
|
@@ -23,13 +24,20 @@ var KvKeyCache = class {
|
|
|
23
24
|
keyId.href
|
|
24
25
|
];
|
|
25
26
|
}
|
|
27
|
+
#entryKey(keyId) {
|
|
28
|
+
return [
|
|
29
|
+
...this.prefix,
|
|
30
|
+
KEY_CACHE_GENERATION,
|
|
31
|
+
keyId.href
|
|
32
|
+
];
|
|
33
|
+
}
|
|
26
34
|
async get(keyId) {
|
|
27
35
|
const negativeExpiration = this.nullKeys.get(keyId.href);
|
|
28
36
|
if (negativeExpiration != null) {
|
|
29
37
|
if (Temporal.Now.instant().until(negativeExpiration).sign >= 0) return null;
|
|
30
38
|
this.nullKeys.delete(keyId.href);
|
|
31
39
|
}
|
|
32
|
-
const serialized = await this.kv.get(
|
|
40
|
+
const serialized = await this.kv.get(this.#entryKey(keyId));
|
|
33
41
|
if (serialized === void 0) return void 0;
|
|
34
42
|
if (serialized === null) {
|
|
35
43
|
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
|
|
@@ -41,7 +49,7 @@ var KvKeyCache = class {
|
|
|
41
49
|
try {
|
|
42
50
|
return await Multikey.fromJsonLd(serialized, this.options);
|
|
43
51
|
} catch {
|
|
44
|
-
await this.kv.delete(
|
|
52
|
+
await this.kv.delete(this.#entryKey(keyId));
|
|
45
53
|
return;
|
|
46
54
|
}
|
|
47
55
|
}
|
|
@@ -49,12 +57,12 @@ var KvKeyCache = class {
|
|
|
49
57
|
async set(keyId, key) {
|
|
50
58
|
if (key == null) {
|
|
51
59
|
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
|
|
52
|
-
await this.kv.set(
|
|
60
|
+
await this.kv.set(this.#entryKey(keyId), null, { ttl: this.unavailableKeyTtl });
|
|
53
61
|
return;
|
|
54
62
|
}
|
|
55
63
|
this.nullKeys.delete(keyId.href);
|
|
56
64
|
const serialized = await key.toJsonLd(this.options);
|
|
57
|
-
await this.kv.set(
|
|
65
|
+
await this.kv.set(this.#entryKey(keyId), serialized);
|
|
58
66
|
}
|
|
59
67
|
async getFetchError(keyId) {
|
|
60
68
|
const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { Temporal } = require("@js-temporal/polyfill");
|
|
2
2
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
3
3
|
require("./chunk-DDcVe30Y.cjs");
|
|
4
|
-
const require_http = require("./http-
|
|
4
|
+
const require_http = require("./http-7FmLhLDv.cjs");
|
|
5
5
|
let _logtape_logtape = require("@logtape/logtape");
|
|
6
6
|
let es_toolkit = require("es-toolkit");
|
|
7
7
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import {
|
|
3
|
+
import { f as validateCryptoKey, t as doubleKnock } from "./http-Chr54tTL.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
5
|
import { curry } from "es-toolkit";
|
|
6
6
|
import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import {
|
|
4
|
+
import { n as version, t as name } from "./deno-BXkm_rws.mjs";
|
|
5
|
+
import { r as fetchKey, s as validateCryptoKey } from "./key-A8SuFznD.mjs";
|
|
6
6
|
import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
|
|
7
7
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
8
8
|
import { getDocumentLoader } from "@fedify/vocab-runtime";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
const { Temporal } = require("@js-temporal/polyfill");
|
|
2
2
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
3
|
-
const require_middleware = require("./middleware-
|
|
3
|
+
const require_middleware = require("./middleware-CCYqHlzn.cjs");
|
|
4
4
|
exports.FederationImpl = require_middleware.FederationImpl;
|