@fedify/fedify 2.1.23 → 2.1.25
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-BlCsjkrX.mjs → builder-DsBAef6b.mjs} +3 -3
- package/dist/{client-CZWLyLmp.mjs → client-DM72NZuk.mjs} +85 -7
- 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-B_Gwkjh7.mjs → deno-9AaebKOA.mjs} +1 -1
- package/dist/docloader-DX96uPUj.mjs +78 -0
- package/dist/federation/builder.test.mjs +3 -3
- package/dist/federation/collection.test.mjs +2 -2
- package/dist/federation/handler.test.mjs +128 -10
- package/dist/federation/idempotency.test.mjs +5 -5
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/keycache.test.mjs +44 -10
- package/dist/federation/kv.test.mjs +2 -2
- package/dist/federation/middleware.test.mjs +227 -12
- 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 +99 -44
- package/dist/federation/negotiation.test.mjs +3 -3
- package/dist/federation/retry.test.mjs +1 -1
- package/dist/federation/router.test.mjs +2 -2
- package/dist/federation/send.test.mjs +202 -7
- package/dist/federation/temporal.test.mjs +3 -3
- package/dist/federation/webfinger.test.mjs +3 -3
- package/dist/{http-DIh9_s_-.mjs → http-B5qAeoJd.mjs} +8 -4
- package/dist/{http-Yoq0YGz7.cjs → http-BJEMpnnY.cjs} +171 -7
- package/dist/{http-88deUx3Y.js → http-CdJ6n4rA.js} +160 -8
- 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/{inbox-BxCHIWPD.mjs → inbox-BSyjaFbo.mjs} +1 -1
- package/dist/{key-DywPPmb0.mjs → key-x9f-H7eO.mjs} +154 -6
- package/dist/{keycache-CCSwkQcY.mjs → keycache-CaO7wyQA.mjs} +12 -4
- package/dist/{kv-cache-HA011rBG.cjs → kv-cache-BDp2c_QE.cjs} +34 -8
- package/dist/{kv-cache-rHQOc5rE.js → kv-cache-Dx3QwNBI.js} +35 -9
- package/dist/{ld-Ch6FVq7X.mjs → ld-cfUwoi0C.mjs} +3 -3
- package/dist/{middleware-itcr4fAv.cjs → middleware-BXJpAXhO.cjs} +1 -1
- package/dist/{middleware-CYsQG25-.js → middleware-CP35ElZv.js} +99 -14
- package/dist/{middleware-DABFg_Xy.mjs → middleware-DDT9ljTQ.mjs} +1 -1
- package/dist/{middleware-D2O8b03q.mjs → middleware-DHHRwSZH.mjs} +31 -20
- package/dist/{middleware-CUNIlYzF.cjs → middleware-XXuSt_6b.cjs} +99 -14
- 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-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-DkE98l_7.d.cts} +3 -1
- package/dist/{mod-BTWFtZwa.d.ts → mod-DqkY4FDr.d.ts} +3 -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 +77 -2
- package/dist/nodeinfo/handler.test.mjs +3 -3
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/nodeinfo/types.test.mjs +2 -2
- package/dist/otel/exporter.test.mjs +2 -2
- package/dist/{owner-DEvZuyOE.d.cts → owner-B3hNQCG8.d.cts} +11 -0
- package/dist/{owner-CnngXDNJ.d.ts → owner-CiBPcC6F.d.ts} +11 -0
- package/dist/owner-DklgvZXn.mjs +175 -0
- package/dist/{proof-Dt5GV-xi.mjs → proof-CI4rsVLu.mjs} +2 -2
- package/dist/{proof-K3inAqnp.js → proof-Dsa_ZC-M.js} +113 -59
- package/dist/{proof-CEn2R6W9.cjs → proof-DzFzyfIc.cjs} +113 -59
- package/dist/{send-CqaJDT2z.mjs → send-DJmW6Z-I.mjs} +73 -6
- package/dist/sig/accept.test.mjs +1 -1
- package/dist/sig/http.test.mjs +45 -5
- package/dist/sig/key.test.mjs +171 -4
- package/dist/sig/ld.test.mjs +59 -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 +106 -7
- package/dist/sig/proof.test.mjs +73 -4
- package/dist/{std__assert-Duiq_YC9.mjs → std__assert-BUOJXi-Z.mjs} +1 -26
- package/dist/{temporal-G_GDghC-.mjs → temporal-BanFifxu.mjs} +1 -1
- package/dist/{types-m8u9L86X.cjs → types-DRPjmHVV.cjs} +101 -5
- package/dist/{types-DTONwNqL.js → types-miAYfiGO.js} +85 -7
- package/dist/utils/docloader.test.mjs +325 -5
- 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/dist/vocab/cjs.test.mjs +1 -1
- package/package.json +7 -7
- package/dist/assert-ddO5KLpe.mjs +0 -23
- package/dist/docloader-B-IG1dVk.mjs +0 -52
- package/dist/owner-Cygg-fHW.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
|
@@ -3,17 +3,18 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { t as esm_default } from "../esm-DVILvP5e.mjs";
|
|
5
5
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
6
|
-
import "../std__assert-
|
|
6
|
+
import "../std__assert-BUOJXi-Z.mjs";
|
|
7
7
|
import { n as assertFalse, t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
|
|
8
|
-
import {
|
|
8
|
+
import { n as assertInstanceOf, t as assert } from "../assert-BhQd8QHP.mjs";
|
|
9
9
|
import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { t as
|
|
14
|
-
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-CqaJDT2z.mjs";
|
|
10
|
+
import { l as verifyRequest } from "../http-B5qAeoJd.mjs";
|
|
11
|
+
import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-CtZLJq76.mjs";
|
|
12
|
+
import { t as doesActorOwnKey } from "../owner-DklgvZXn.mjs";
|
|
13
|
+
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-DJmW6Z-I.mjs";
|
|
15
14
|
import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
16
15
|
import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
|
|
16
|
+
import { FetchError, UrlError } from "@fedify/vocab-runtime";
|
|
17
|
+
import dns from "node:dns/promises";
|
|
17
18
|
//#region src/federation/send.test.ts
|
|
18
19
|
test("extractInboxes()", () => {
|
|
19
20
|
const recipients = [
|
|
@@ -415,5 +416,199 @@ test("sendActivity() records OpenTelemetry span events", async (t) => {
|
|
|
415
416
|
esm_default.hardReset();
|
|
416
417
|
});
|
|
417
418
|
});
|
|
419
|
+
for (const signed of [false, true]) {
|
|
420
|
+
const keys = [{
|
|
421
|
+
privateKey: signed ? rsaPrivateKey2 : ed25519PrivateKey,
|
|
422
|
+
keyId: signed ? rsaPublicKey2.id : ed25519Multikey.id
|
|
423
|
+
}];
|
|
424
|
+
test(`sendActivity() validates destinations (signed: ${signed})`, async (t) => {
|
|
425
|
+
const activity = {
|
|
426
|
+
type: "Create",
|
|
427
|
+
id: "https://example.com/activity"
|
|
428
|
+
};
|
|
429
|
+
const publicInbox = "https://8.8.8.8/inbox";
|
|
430
|
+
const privateInbox = "http://127.0.0.1/inbox";
|
|
431
|
+
for (const target of [
|
|
432
|
+
privateInbox,
|
|
433
|
+
"http://169.254.169.254/",
|
|
434
|
+
"http://[::1]/"
|
|
435
|
+
]) await t.step(`rejects ${target} before fetching`, async () => {
|
|
436
|
+
esm_default.mockGlobal().catch(202);
|
|
437
|
+
try {
|
|
438
|
+
await assertRejects(() => sendActivity({
|
|
439
|
+
activity,
|
|
440
|
+
keys,
|
|
441
|
+
inbox: new URL(target)
|
|
442
|
+
}), UrlError);
|
|
443
|
+
assertEquals(esm_default.callHistory.calls().length, 0);
|
|
444
|
+
} finally {
|
|
445
|
+
esm_default.hardReset();
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
for (const status of [
|
|
449
|
+
301,
|
|
450
|
+
302,
|
|
451
|
+
303,
|
|
452
|
+
307,
|
|
453
|
+
308
|
|
454
|
+
]) for (const allowPrivateAddress of [false, true]) await t.step(`${status} private redirect, opt-in: ${allowPrivateAddress}`, async () => {
|
|
455
|
+
esm_default.mockGlobal();
|
|
456
|
+
esm_default.route(publicInbox, {
|
|
457
|
+
status,
|
|
458
|
+
headers: { Location: privateInbox }
|
|
459
|
+
});
|
|
460
|
+
esm_default.route(privateInbox, 202);
|
|
461
|
+
try {
|
|
462
|
+
const send = () => sendActivity({
|
|
463
|
+
activity,
|
|
464
|
+
keys,
|
|
465
|
+
inbox: new URL(publicInbox),
|
|
466
|
+
allowPrivateAddress
|
|
467
|
+
});
|
|
468
|
+
if (allowPrivateAddress) {
|
|
469
|
+
await send();
|
|
470
|
+
const calls = esm_default.callHistory.calls(privateInbox);
|
|
471
|
+
assertEquals(calls.length, 1);
|
|
472
|
+
const request = calls[0].request;
|
|
473
|
+
const preservesBody = signed || status === 307 || status === 308;
|
|
474
|
+
assertEquals(request.method, preservesBody ? "POST" : "GET");
|
|
475
|
+
assertEquals(await request.clone().text(), preservesBody ? JSON.stringify(activity) : "");
|
|
476
|
+
} else {
|
|
477
|
+
await assertRejects(send, UrlError);
|
|
478
|
+
assertEquals(esm_default.callHistory.calls(privateInbox).length, 0);
|
|
479
|
+
}
|
|
480
|
+
} finally {
|
|
481
|
+
esm_default.hardReset();
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
await t.step("rejects a private destination after a public redirect", async () => {
|
|
485
|
+
esm_default.mockGlobal().route(publicInbox, {
|
|
486
|
+
status: 307,
|
|
487
|
+
headers: { Location: "/next" }
|
|
488
|
+
}).route("https://8.8.8.8/next", {
|
|
489
|
+
status: 308,
|
|
490
|
+
headers: { Location: privateInbox }
|
|
491
|
+
}).route(privateInbox, 202);
|
|
492
|
+
try {
|
|
493
|
+
await assertRejects(() => sendActivity({
|
|
494
|
+
activity,
|
|
495
|
+
keys,
|
|
496
|
+
inbox: new URL(publicInbox)
|
|
497
|
+
}), UrlError);
|
|
498
|
+
assertEquals(esm_default.callHistory.calls(privateInbox).length, 0);
|
|
499
|
+
} finally {
|
|
500
|
+
esm_default.hardReset();
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
await t.step("limits redirect loops", async () => {
|
|
504
|
+
esm_default.mockGlobal().route(publicInbox, {
|
|
505
|
+
status: 307,
|
|
506
|
+
headers: { Location: publicInbox }
|
|
507
|
+
});
|
|
508
|
+
try {
|
|
509
|
+
await assertRejects(() => sendActivity({
|
|
510
|
+
activity,
|
|
511
|
+
keys,
|
|
512
|
+
inbox: new URL(publicInbox)
|
|
513
|
+
}));
|
|
514
|
+
assert(esm_default.callHistory.calls().length <= 21);
|
|
515
|
+
} finally {
|
|
516
|
+
esm_default.hardReset();
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
await t.step("allows a direct private inbox with opt-in", async () => {
|
|
520
|
+
esm_default.mockGlobal().route(privateInbox, 202);
|
|
521
|
+
try {
|
|
522
|
+
await sendActivity({
|
|
523
|
+
activity,
|
|
524
|
+
keys,
|
|
525
|
+
inbox: new URL(privateInbox),
|
|
526
|
+
allowPrivateAddress: true
|
|
527
|
+
});
|
|
528
|
+
assertEquals(esm_default.callHistory.calls(privateInbox).length, 1);
|
|
529
|
+
} finally {
|
|
530
|
+
esm_default.hardReset();
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
await t.step("allows relative public redirects", async () => {
|
|
534
|
+
esm_default.mockGlobal().route(publicInbox, {
|
|
535
|
+
status: 307,
|
|
536
|
+
headers: { Location: "/next" }
|
|
537
|
+
}).route("https://8.8.8.8/next", 202);
|
|
538
|
+
try {
|
|
539
|
+
await sendActivity({
|
|
540
|
+
activity,
|
|
541
|
+
keys,
|
|
542
|
+
inbox: new URL(publicInbox)
|
|
543
|
+
});
|
|
544
|
+
assertEquals(esm_default.callHistory.calls("https://8.8.8.8/next").length, 1);
|
|
545
|
+
} finally {
|
|
546
|
+
esm_default.hardReset();
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
for (const signed of [false, true]) test({
|
|
552
|
+
name: `sendActivity() classifies DNS failures (signed: ${signed})`,
|
|
553
|
+
ignore: "Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted",
|
|
554
|
+
async fn(t) {
|
|
555
|
+
const activity = {
|
|
556
|
+
type: "Create",
|
|
557
|
+
id: "https://example.com/activity"
|
|
558
|
+
};
|
|
559
|
+
const destination = "https://delivery.invalid/inbox";
|
|
560
|
+
const publicInbox = "https://8.8.8.8/inbox";
|
|
561
|
+
const keys = signed ? [{
|
|
562
|
+
privateKey: rsaPrivateKey2,
|
|
563
|
+
keyId: rsaPublicKey2.id
|
|
564
|
+
}] : [];
|
|
565
|
+
for (const result of [
|
|
566
|
+
"throws",
|
|
567
|
+
"empty",
|
|
568
|
+
"cname",
|
|
569
|
+
"private"
|
|
570
|
+
]) for (const redirected of [false, true]) await t.step(`${result}, redirected: ${redirected}`, async () => {
|
|
571
|
+
const originalLookup = dns.lookup;
|
|
572
|
+
const resolverError = /* @__PURE__ */ new Error("Resolver unavailable");
|
|
573
|
+
const lookups = [];
|
|
574
|
+
dns.lookup = ((hostname, options) => {
|
|
575
|
+
lookups.push(hostname);
|
|
576
|
+
assertEquals(hostname, "delivery.invalid");
|
|
577
|
+
assertEquals(options, { all: true });
|
|
578
|
+
if (result === "throws") return Promise.reject(resolverError);
|
|
579
|
+
return Promise.resolve(result === "empty" ? [] : [{
|
|
580
|
+
address: result === "private" ? "127.0.0.1" : "alias.invalid.",
|
|
581
|
+
family: 4
|
|
582
|
+
}]);
|
|
583
|
+
});
|
|
584
|
+
try {
|
|
585
|
+
esm_default.mockGlobal().catch(202);
|
|
586
|
+
if (redirected) esm_default.route(publicInbox, {
|
|
587
|
+
status: 307,
|
|
588
|
+
headers: { Location: destination }
|
|
589
|
+
});
|
|
590
|
+
const send = () => sendActivity({
|
|
591
|
+
activity,
|
|
592
|
+
keys,
|
|
593
|
+
inbox: new URL(redirected ? publicInbox : destination)
|
|
594
|
+
});
|
|
595
|
+
if (result === "private") assertEquals((await assertRejects(send, UrlError)).reason, "disallowed");
|
|
596
|
+
else {
|
|
597
|
+
const error = await assertRejects(send, FetchError);
|
|
598
|
+
assertEquals(error.url.href, destination);
|
|
599
|
+
assertInstanceOf(error.cause, UrlError);
|
|
600
|
+
assertEquals(error.cause.reason, "dns");
|
|
601
|
+
assertEquals(error.cause.cause, result === "throws" ? resolverError : void 0);
|
|
602
|
+
}
|
|
603
|
+
assertEquals(lookups, ["delivery.invalid"]);
|
|
604
|
+
assertEquals(esm_default.callHistory.calls(destination).length, 0);
|
|
605
|
+
assertEquals(esm_default.callHistory.calls().length, redirected ? 1 : 0);
|
|
606
|
+
} finally {
|
|
607
|
+
dns.lookup = originalLookup;
|
|
608
|
+
esm_default.hardReset();
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
});
|
|
418
613
|
//#endregion
|
|
419
614
|
export {};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import "../std__assert-
|
|
4
|
+
import "../std__assert-BUOJXi-Z.mjs";
|
|
5
5
|
import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
|
|
6
|
-
import { t as assert } from "../assert-
|
|
7
|
-
import { t as hasMalformedKnownTemporalLiteral } from "../temporal-
|
|
6
|
+
import { t as assert } from "../assert-BhQd8QHP.mjs";
|
|
7
|
+
import { t as hasMalformedKnownTemporalLiteral } from "../temporal-BanFifxu.mjs";
|
|
8
8
|
import { test } from "@fedify/fixture";
|
|
9
9
|
//#region src/federation/temporal.test.ts
|
|
10
10
|
test("hasMalformedKnownTemporalLiteral() detects expanded proof timestamps", async () => {
|
|
@@ -3,9 +3,9 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { n as createRequestContext } from "../context-Juj6bdHC.mjs";
|
|
5
5
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
6
|
-
import "../std__assert-
|
|
7
|
-
import { t as MemoryKvStore } from "../kv-
|
|
8
|
-
import { a as createFederation, o as handleWebFinger } from "../middleware-
|
|
6
|
+
import "../std__assert-BUOJXi-Z.mjs";
|
|
7
|
+
import { t as MemoryKvStore } from "../kv-BrZHNugx.mjs";
|
|
8
|
+
import { a as createFederation, o as handleWebFinger } from "../middleware-DHHRwSZH.mjs";
|
|
9
9
|
import { test } from "@fedify/fixture";
|
|
10
10
|
import { Image, Link, Person } from "@fedify/vocab";
|
|
11
11
|
//#region src/federation/webfinger.test.ts
|
|
@@ -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-9AaebKOA.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-x9f-H7eO.mjs";
|
|
7
7
|
import { CryptographicKey } from "@fedify/vocab";
|
|
8
8
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
9
9
|
import { FetchError } from "@fedify/vocab-runtime";
|
|
@@ -674,6 +674,8 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
674
674
|
return invalidSignatureResult(null);
|
|
675
675
|
}
|
|
676
676
|
let failure = noSignatureResult();
|
|
677
|
+
let body = null;
|
|
678
|
+
let digestValid = null;
|
|
677
679
|
for (const sigName of signatureNames) {
|
|
678
680
|
if (!signatures[sigName]) {
|
|
679
681
|
failure = invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId));
|
|
@@ -725,7 +727,9 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
725
727
|
failure = invalidSignatureResult(keyId);
|
|
726
728
|
continue;
|
|
727
729
|
}
|
|
728
|
-
|
|
730
|
+
body ??= await request.arrayBuffer();
|
|
731
|
+
digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
|
|
732
|
+
if (!digestValid) {
|
|
729
733
|
logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
|
|
730
734
|
failure = invalidSignatureResult(keyId);
|
|
731
735
|
continue;
|
|
@@ -5,13 +5,13 @@ let _logtape_logtape = require("@logtape/logtape");
|
|
|
5
5
|
let _fedify_vocab = require("@fedify/vocab");
|
|
6
6
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
7
7
|
let byte_encodings_hex = require("byte-encodings/hex");
|
|
8
|
-
let structured_field_values = require("structured-field-values");
|
|
9
8
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
9
|
+
let structured_field_values = require("structured-field-values");
|
|
10
10
|
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
11
11
|
let byte_encodings_base64 = require("byte-encodings/base64");
|
|
12
12
|
//#region deno.json
|
|
13
13
|
var name = "@fedify/fedify";
|
|
14
|
-
var version = "2.1.
|
|
14
|
+
var version = "2.1.25";
|
|
15
15
|
//#endregion
|
|
16
16
|
//#region src/sig/accept.ts
|
|
17
17
|
/**
|
|
@@ -231,6 +231,118 @@ async function importJwk(jwk, type) {
|
|
|
231
231
|
validateCryptoKey(key, type);
|
|
232
232
|
return key;
|
|
233
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Fetches the document that the given actor URI dereferences to, and returns
|
|
236
|
+
* the actor only if the host that served the document is authoritative for
|
|
237
|
+
* the id the document claims.
|
|
238
|
+
*
|
|
239
|
+
* Only the origin that serves an actor id can speak for it. Without that
|
|
240
|
+
* rule any host could serve a document describing somebody else's actor—and
|
|
241
|
+
* listing its own keys as that actor's.
|
|
242
|
+
*
|
|
243
|
+
* @param actorId The URI of the actor to fetch.
|
|
244
|
+
* @param options Options for fetching the document.
|
|
245
|
+
* @returns The actor, or `null` if the document cannot be fetched, is not an
|
|
246
|
+
* actor, or belongs to another origin.
|
|
247
|
+
* @internal
|
|
248
|
+
*/
|
|
249
|
+
async function fetchActorDocument(actorId, options = {}) {
|
|
250
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
251
|
+
"fedify",
|
|
252
|
+
"sig",
|
|
253
|
+
"key"
|
|
254
|
+
]);
|
|
255
|
+
const documentLoader = options.documentLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)();
|
|
256
|
+
const contextLoader = options.contextLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)();
|
|
257
|
+
const { tracerProvider } = options;
|
|
258
|
+
let document;
|
|
259
|
+
let documentUrl = actorId;
|
|
260
|
+
try {
|
|
261
|
+
const remoteDocument = await documentLoader(actorId.href);
|
|
262
|
+
document = remoteDocument.document;
|
|
263
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", actorId);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
logger.debug("Failed to fetch the actor {actorId}: {error}", {
|
|
266
|
+
actorId: actorId.href,
|
|
267
|
+
error
|
|
268
|
+
});
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
let object;
|
|
272
|
+
try {
|
|
273
|
+
object = await _fedify_vocab.Object.fromJsonLd(document, {
|
|
274
|
+
documentLoader,
|
|
275
|
+
contextLoader,
|
|
276
|
+
tracerProvider,
|
|
277
|
+
baseUrl: documentUrl
|
|
278
|
+
});
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if (!(error instanceof TypeError)) throw error;
|
|
281
|
+
logger.debug("The document served at {documentUrl} is not a valid object: {error}", {
|
|
282
|
+
documentUrl: documentUrl.href,
|
|
283
|
+
error
|
|
284
|
+
});
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
if (!(0, _fedify_vocab.isActor)(object)) return null;
|
|
288
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
289
|
+
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.", {
|
|
290
|
+
documentUrl: documentUrl.href,
|
|
291
|
+
actorId: object.id?.href
|
|
292
|
+
});
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
return object;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Resolves the owner that the given key claims, and returns it only if the
|
|
299
|
+
* claim holds.
|
|
300
|
+
*
|
|
301
|
+
* The `owner` of a {@link CryptographicKey}, like the `controller` of
|
|
302
|
+
* a {@link Multikey}, proves nothing on its own: the key document and the
|
|
303
|
+
* claim inside it are served by the same host, so anyone able to serve a key
|
|
304
|
+
* document can name any actor in the world as its owner. The claim becomes
|
|
305
|
+
* meaningful only once the named actor's own document is fetched and turns
|
|
306
|
+
* out to link back to the key. This function performs that mutual-link
|
|
307
|
+
* check, which is the only sound way to learn a fetched key's owner.
|
|
308
|
+
*
|
|
309
|
+
* The claimed owner is always dereferenced, never read out of the key
|
|
310
|
+
* document: an owner embedded there is written by whoever wrote the key.
|
|
311
|
+
*
|
|
312
|
+
* @param key The key whose ownership claim is to be verified. It must carry
|
|
313
|
+
* an `id`, as that is what the owner has to link back to.
|
|
314
|
+
* @param options Options for fetching the claimed owner.
|
|
315
|
+
* @returns The verified owner, or `null` if the key claims no owner, the
|
|
316
|
+
* owner cannot be fetched, or the owner does not link back to
|
|
317
|
+
* the key.
|
|
318
|
+
* @internal
|
|
319
|
+
*/
|
|
320
|
+
async function verifyKeyOwnership(key, options = {}) {
|
|
321
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
322
|
+
"fedify",
|
|
323
|
+
"sig",
|
|
324
|
+
"key"
|
|
325
|
+
]);
|
|
326
|
+
const keyId = key.id;
|
|
327
|
+
if (keyId == null) return null;
|
|
328
|
+
const claimedOwnerId = key instanceof _fedify_vocab.CryptographicKey ? key.ownerId : key.controllerId;
|
|
329
|
+
if (claimedOwnerId == null) return null;
|
|
330
|
+
const owner = await fetchActorDocument(claimedOwnerId, options);
|
|
331
|
+
if (owner == null) {
|
|
332
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
|
|
333
|
+
keyId: keyId.href,
|
|
334
|
+
claimedOwnerId: claimedOwnerId.href
|
|
335
|
+
});
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
const linkedKeyIds = key instanceof _fedify_vocab.CryptographicKey ? owner.publicKeyIds : owner.assertionMethodIds;
|
|
339
|
+
for (const linkedKeyId of linkedKeyIds) if (linkedKeyId.href === keyId.href) return owner;
|
|
340
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims does not list the key as its own.", {
|
|
341
|
+
keyId: keyId.href,
|
|
342
|
+
claimedOwnerId: claimedOwnerId.href
|
|
343
|
+
});
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
234
346
|
async function withFetchKeySpan(keyId, tracerProvider, fetcher) {
|
|
235
347
|
tracerProvider ??= _opentelemetry_api.trace.getTracerProvider();
|
|
236
348
|
return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.fetch_key", {
|
|
@@ -358,7 +470,7 @@ async function getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger) {
|
|
|
358
470
|
async function clearFetchErrorMetadata(keyId, keyCache) {
|
|
359
471
|
await keyCache?.setFetchError?.(keyId, null);
|
|
360
472
|
}
|
|
361
|
-
async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
|
|
473
|
+
async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
|
|
362
474
|
let object;
|
|
363
475
|
try {
|
|
364
476
|
object = await _fedify_vocab.Object.fromJsonLd(document, {
|
|
@@ -388,8 +500,22 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
388
500
|
}
|
|
389
501
|
}
|
|
390
502
|
let key = null;
|
|
391
|
-
|
|
503
|
+
let ownerDocument = null;
|
|
504
|
+
if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object.id == null ? object.clone({ id: cacheKey }) : object;
|
|
392
505
|
else if ((0, _fedify_vocab.isActor)(object)) {
|
|
506
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
507
|
+
logger.debug("Failed to verify; the document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin.", {
|
|
508
|
+
keyId,
|
|
509
|
+
documentUrl: documentUrl.href,
|
|
510
|
+
actorId: object.id?.href
|
|
511
|
+
});
|
|
512
|
+
await keyCache?.set(cacheKey, null);
|
|
513
|
+
return {
|
|
514
|
+
key: null,
|
|
515
|
+
cached: false
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
ownerDocument = object;
|
|
393
519
|
const keys = cls === _fedify_vocab.CryptographicKey ? object.getPublicKeys({
|
|
394
520
|
documentLoader,
|
|
395
521
|
contextLoader,
|
|
@@ -451,6 +577,25 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
451
577
|
cached: false
|
|
452
578
|
};
|
|
453
579
|
}
|
|
580
|
+
const claimedOwnerId = key instanceof _fedify_vocab.CryptographicKey ? key.ownerId : key.controllerId;
|
|
581
|
+
if (ownerDocument != null && claimedOwnerId == null) key = key instanceof _fedify_vocab.CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
|
|
582
|
+
else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
|
|
583
|
+
if (await verifyKeyOwnership(key, {
|
|
584
|
+
documentLoader,
|
|
585
|
+
contextLoader,
|
|
586
|
+
tracerProvider
|
|
587
|
+
}) == null) {
|
|
588
|
+
logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
|
|
589
|
+
keyId,
|
|
590
|
+
claimedOwnerId: claimedOwnerId.href
|
|
591
|
+
});
|
|
592
|
+
await keyCache?.set(cacheKey, null);
|
|
593
|
+
return {
|
|
594
|
+
key: null,
|
|
595
|
+
cached: false
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
}
|
|
454
599
|
if (keyCache != null) {
|
|
455
600
|
await keyCache.set(cacheKey, key);
|
|
456
601
|
logger.debug("Key {keyId} cached.", { keyId });
|
|
@@ -474,12 +619,15 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
|
|
|
474
619
|
if (cached != null) return cached;
|
|
475
620
|
logger.debug("Fetching key {keyId} to verify signature...", { keyId });
|
|
476
621
|
let document;
|
|
622
|
+
let documentUrl = cacheKey;
|
|
477
623
|
try {
|
|
478
|
-
|
|
624
|
+
const remoteDocument = await (options.documentLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)())(keyId);
|
|
625
|
+
document = remoteDocument.document;
|
|
626
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", cacheKey);
|
|
479
627
|
} catch (error) {
|
|
480
628
|
return await onFetchError(error, cacheKey, keyId, keyCache, logger);
|
|
481
629
|
}
|
|
482
|
-
return await resolveFetchedKey(document, cacheKey, keyId, cls, options, logger);
|
|
630
|
+
return await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
|
|
483
631
|
}
|
|
484
632
|
async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
485
633
|
return await fetchKeyWithResult(typeof keyId === "string" ? new URL(keyId) : keyId, cls, options, (_cacheKey, _keyId, _keyCache, _logger) => {
|
|
@@ -1167,6 +1315,8 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1167
1315
|
return invalidSignatureResult(null);
|
|
1168
1316
|
}
|
|
1169
1317
|
let failure = noSignatureResult();
|
|
1318
|
+
let body = null;
|
|
1319
|
+
let digestValid = null;
|
|
1170
1320
|
for (const sigName of signatureNames) {
|
|
1171
1321
|
if (!signatures[sigName]) {
|
|
1172
1322
|
failure = invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId));
|
|
@@ -1218,7 +1368,9 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1218
1368
|
failure = invalidSignatureResult(keyId);
|
|
1219
1369
|
continue;
|
|
1220
1370
|
}
|
|
1221
|
-
|
|
1371
|
+
body ??= await request.arrayBuffer();
|
|
1372
|
+
digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
|
|
1373
|
+
if (!digestValid) {
|
|
1222
1374
|
logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
|
|
1223
1375
|
failure = invalidSignatureResult(keyId);
|
|
1224
1376
|
continue;
|
|
@@ -1544,6 +1696,12 @@ Object.defineProperty(exports, "exportJwk", {
|
|
|
1544
1696
|
return exportJwk;
|
|
1545
1697
|
}
|
|
1546
1698
|
});
|
|
1699
|
+
Object.defineProperty(exports, "fetchActorDocument", {
|
|
1700
|
+
enumerable: true,
|
|
1701
|
+
get: function() {
|
|
1702
|
+
return fetchActorDocument;
|
|
1703
|
+
}
|
|
1704
|
+
});
|
|
1547
1705
|
Object.defineProperty(exports, "fetchKey", {
|
|
1548
1706
|
enumerable: true,
|
|
1549
1707
|
get: function() {
|
|
@@ -1616,6 +1774,12 @@ Object.defineProperty(exports, "validateCryptoKey", {
|
|
|
1616
1774
|
return validateCryptoKey;
|
|
1617
1775
|
}
|
|
1618
1776
|
});
|
|
1777
|
+
Object.defineProperty(exports, "verifyKeyOwnership", {
|
|
1778
|
+
enumerable: true,
|
|
1779
|
+
get: function() {
|
|
1780
|
+
return verifyKeyOwnership;
|
|
1781
|
+
}
|
|
1782
|
+
});
|
|
1619
1783
|
Object.defineProperty(exports, "verifyRequest", {
|
|
1620
1784
|
enumerable: true,
|
|
1621
1785
|
get: function() {
|