@fedify/fedify 2.4.0-dev.1964 → 2.4.0-dev.1995
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-B4zpKIFU.mjs → builder-D0DxgOV5.mjs} +2 -2
- package/dist/{client-CtxvmazV.mjs → client-DpnpgjyX.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 +1 -1
- package/dist/compound-proof-CJnWc6c-.mjs +495 -0
- package/dist/{context-COLwHogJ.d.cts → context-BeDnDuF3.d.cts} +4 -3
- package/dist/{context-DiiCt4S7.d.ts → context-uyRUWoZQ.d.ts} +4 -3
- package/dist/{deno-CtG93ohj.mjs → deno-CABhzCb_.mjs} +1 -1
- package/dist/{docloader-dk_k3S8d.mjs → docloader-BbGSL0Xq.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler-compound-proof.test.d.mts +2 -0
- package/dist/federation/handler-compound-proof.test.mjs +152 -0
- package/dist/federation/handler.test.mjs +122 -4
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/keycache.test.mjs +52 -9
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +111 -15
- 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/send.test.mjs +139 -4
- 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-Drjlrrax.cjs → federation-DCEHQk4G.cjs} +632 -57
- package/dist/{http-6TI0tI03.d.cts → http-65PYi9jY.d.ts} +34 -2
- package/dist/{http-Dj4s1UQ0.js → http-9WyuldYh.js} +170 -8
- package/dist/{http-CdrSn2ip.d.ts → http-CP-7OdSq.d.cts} +34 -2
- package/dist/{http-B3skRvGo.cjs → http-CRFns-UE.cjs} +181 -7
- package/dist/{http-D7LX-BL9.mjs → http-D9urSO2D.mjs} +3 -3
- package/dist/{key-BVjgbs_9.mjs → key-Ccs8cnd7.mjs} +169 -7
- package/dist/{keycache-ZLqIfrkL.mjs → keycache-BUDHB5rY.mjs} +12 -4
- package/dist/{kv-cache-DtsjH_bY.cjs → kv-cache-DXT9vmIg.cjs} +2 -2
- package/dist/{kv-cache-tGNbvDCF.js → kv-cache-DtmXhPXG.js} +2 -2
- package/dist/{kv-cache-xRcC_xxP.mjs → kv-cache-lKyFn_Dn.mjs} +1 -1
- package/dist/{ld-OuCeQFny.mjs → ld-qup__MAW.mjs} +3 -3
- package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
- package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
- package/dist/{metrics-B9N27cCB.mjs → metrics-CISXCvB1.mjs} +1 -1
- package/dist/{middleware-TeLjzoxn.mjs → middleware-CzvJvPHK.mjs} +58 -15
- package/dist/{middleware-Cd_vPqDH.mjs → middleware-DkrU2WhY.mjs} +1 -1
- package/dist/{middleware-CPTDFMmJ.js → middleware-DsfWajaD.js} +593 -18
- package/dist/{mod-DypxPS9H.d.ts → mod-C0BYc0OU.d.ts} +1 -1
- package/dist/{mod-CnGLNoVw.d.cts → mod-CbKDOnCt.d.cts} +1 -1
- package/dist/{mod-CLHkCwLm.d.cts → mod-CyiSjRkk.d.cts} +1 -1
- package/dist/{mod-CSH1Ee-I.d.ts → mod-D882_Xsg.d.ts} +1 -1
- package/dist/{mod-DzRXACM5.d.ts → mod-UDwMjuHM.d.ts} +1 -1
- package/dist/{mod-BZldCEU0.d.cts → mod-x9W0d0Pk.d.cts} +1 -1
- package/dist/mod.cjs +20 -20
- 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 +1 -1
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/{owner-DGSfntI-.d.ts → owner-BF0L9cMt.d.cts} +12 -1
- package/dist/{proof-BO_esQii.js → owner-BIPEZGiU.js} +1255 -1098
- package/dist/{owner-r_waTiQj.d.cts → owner-BSzr5-I8.d.ts} +12 -1
- package/dist/owner-CRoq8Spb.mjs +175 -0
- package/dist/{proof-Dua0d906.cjs → owner-CVA7L9MA.cjs} +1266 -1097
- package/dist/{proof-D5Ajk4DJ.mjs → proof-BDxNVZ3E.mjs} +186 -83
- package/dist/{send-DVXyUqhO.mjs → send-BzM5GLyn.mjs} +68 -11
- package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
- package/dist/sig/compound-proof-discovery.test.mjs +331 -0
- package/dist/sig/compound-proof-verification.test.d.mts +2 -0
- package/dist/sig/compound-proof-verification.test.mjs +465 -0
- package/dist/sig/compound-proof.test.d.mts +2 -0
- package/dist/sig/compound-proof.test.mjs +324 -0
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +169 -2
- package/dist/sig/ld.test.mjs +55 -2
- package/dist/sig/mod.cjs +17 -17
- 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 +103 -4
- package/dist/sig/proof.test.mjs +120 -1
- package/dist/{temporal-BoDw51A-.mjs → temporal-Dv6Qv-8k.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +2 -1
- package/dist/{types-RXm5YxpC.cjs → types-Be2zbyeG.cjs} +94 -2
- package/dist/{types-Dtyf8lVj.js → types-C_cNlNVK.js} +78 -4
- package/dist/utils/docloader.test.mjs +27 -3
- 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
- package/dist/owner-DTEqTzSr.mjs +0 -120
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import {
|
|
5
|
-
import { n as doubleKnock } from "./http-
|
|
4
|
+
import { s as validateCryptoKey } from "./key-Ccs8cnd7.mjs";
|
|
5
|
+
import { n as doubleKnock } from "./http-D9urSO2D.mjs";
|
|
6
6
|
import { getLogger } from "@logtape/logtape";
|
|
7
7
|
import { curry } from "es-toolkit";
|
|
8
8
|
import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
@@ -6,7 +6,7 @@ import { t as assertEquals } from "../assert_equals-CBlRfA-_.mjs";
|
|
|
6
6
|
import { t as assertExists } from "../assert_exists-DtzkXC47.mjs";
|
|
7
7
|
import { t as assertThrows } from "../assert_throws-BJ5wuvR2.mjs";
|
|
8
8
|
import "../std__assert-CxFT0MNt.mjs";
|
|
9
|
-
import { r as createFederationBuilder } from "../builder-
|
|
9
|
+
import { r as createFederationBuilder } from "../builder-D0DxgOV5.mjs";
|
|
10
10
|
import { DisallowedOperatorError, DisallowedVarSpecModifierError, DuplicateRouteVariableError, RouteTemplateOptionsNotMatchedError, RouterError } from "@fedify/uri-template";
|
|
11
11
|
import { Activity, Note, Person } from "@fedify/vocab";
|
|
12
12
|
import { test } from "@fedify/fixture";
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { r as createRequestContext, t as createInboxContext } from "../context-CbS35fLr.mjs";
|
|
5
|
+
import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
|
|
6
|
+
import { t as assertEquals } from "../assert_equals-CBlRfA-_.mjs";
|
|
7
|
+
import "../std__assert-CxFT0MNt.mjs";
|
|
8
|
+
import { s as signRequest } from "../http-D9urSO2D.mjs";
|
|
9
|
+
import { a as rsaPrivateKey3, c as rsaPublicKey3 } from "../keys-DIJNHW-Y.mjs";
|
|
10
|
+
import { d as handleInbox, o as createFederation } from "../middleware-CzvJvPHK.mjs";
|
|
11
|
+
import { p as signJsonLd } from "../ld-qup__MAW.mjs";
|
|
12
|
+
import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
|
|
13
|
+
import { t as map_local_create_note_default } from "../map-local-create-note-B0SeXJms.mjs";
|
|
14
|
+
import { Create } from "@fedify/vocab";
|
|
15
|
+
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
16
|
+
import { encodeMultibase } from "@fedify/vocab-runtime";
|
|
17
|
+
import serialize from "json-canon";
|
|
18
|
+
//#region src/federation/handler-compound-proof.test.ts
|
|
19
|
+
async function sha256(value) {
|
|
20
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value)));
|
|
21
|
+
}
|
|
22
|
+
async function secureDocument(unsecuredDocument, privateJwk, verificationMethod) {
|
|
23
|
+
const proofConfiguration = {
|
|
24
|
+
"@context": unsecuredDocument["@context"],
|
|
25
|
+
type: "DataIntegrityProof",
|
|
26
|
+
cryptosuite: "eddsa-jcs-2022",
|
|
27
|
+
verificationMethod,
|
|
28
|
+
proofPurpose: "assertionMethod",
|
|
29
|
+
created: "2023-02-24T23:36:38Z"
|
|
30
|
+
};
|
|
31
|
+
const proofHash = await sha256(serialize(proofConfiguration));
|
|
32
|
+
const documentHash = await sha256(serialize(unsecuredDocument));
|
|
33
|
+
const input = /* @__PURE__ */ new Uint8Array(64);
|
|
34
|
+
input.set(proofHash);
|
|
35
|
+
input.set(documentHash, proofHash.length);
|
|
36
|
+
const privateKey = await crypto.subtle.importKey("jwk", privateJwk, "Ed25519", false, ["sign"]);
|
|
37
|
+
const signature = new Uint8Array(await crypto.subtle.sign("Ed25519", privateKey, input));
|
|
38
|
+
return {
|
|
39
|
+
...unsecuredDocument,
|
|
40
|
+
proof: {
|
|
41
|
+
...proofConfiguration,
|
|
42
|
+
proofValue: new TextDecoder().decode(encodeMultibase("base58btc", signature))
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function requestFor(body) {
|
|
47
|
+
return new Request("https://example.com/inbox", {
|
|
48
|
+
method: "POST",
|
|
49
|
+
body: JSON.stringify(body)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function handle(requestOrBody, dispatched) {
|
|
53
|
+
const request = requestOrBody instanceof Request ? requestOrBody : requestFor(requestOrBody);
|
|
54
|
+
const context = createRequestContext({
|
|
55
|
+
federation: createFederation({ kv: new MemoryKvStore() }),
|
|
56
|
+
request,
|
|
57
|
+
url: new URL(request.url),
|
|
58
|
+
data: void 0,
|
|
59
|
+
documentLoader: mockDocumentLoader,
|
|
60
|
+
contextLoader: mockDocumentLoader
|
|
61
|
+
});
|
|
62
|
+
const inboxListeners = new ActivityListenerSet();
|
|
63
|
+
inboxListeners.add(Create, () => {
|
|
64
|
+
if (dispatched != null) dispatched.count++;
|
|
65
|
+
});
|
|
66
|
+
return await handleInbox(request, {
|
|
67
|
+
recipient: null,
|
|
68
|
+
context,
|
|
69
|
+
inboxContextFactory() {
|
|
70
|
+
return createInboxContext({
|
|
71
|
+
...context,
|
|
72
|
+
clone: void 0
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
kv: new MemoryKvStore(),
|
|
76
|
+
kvPrefixes: {
|
|
77
|
+
activityIdempotence: ["_fedify", "activityIdempotence"],
|
|
78
|
+
publicKey: ["_fedify", "publicKey"],
|
|
79
|
+
acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
|
|
80
|
+
},
|
|
81
|
+
actorDispatcher() {
|
|
82
|
+
return null;
|
|
83
|
+
},
|
|
84
|
+
inboxListeners,
|
|
85
|
+
onNotFound() {
|
|
86
|
+
return new Response("Not found", { status: 404 });
|
|
87
|
+
},
|
|
88
|
+
signatureTimeWindow: { minutes: 5 },
|
|
89
|
+
skipSignatureVerification: false
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
test("handleInbox() enforces portable compound proofs atomically", async () => {
|
|
93
|
+
const valid = structuredClone(map_local_create_note_default.documents.finalSecuredCompound);
|
|
94
|
+
assertEquals((await handle(valid)).status, 202);
|
|
95
|
+
const unsignedInner = structuredClone(map_local_create_note_default.documents.outerUnsecuredDocument);
|
|
96
|
+
delete unsignedInner.object.proof;
|
|
97
|
+
const rejected = await handle(await secureDocument(unsignedInner, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod));
|
|
98
|
+
assertEquals([rejected.status, await rejected.text()], [401, "Failed to verify compound portable Object Integrity Proofs."]);
|
|
99
|
+
const tamperedInner = structuredClone(map_local_create_note_default.documents.outerUnsecuredDocument);
|
|
100
|
+
tamperedInner.object.content = "Tampered";
|
|
101
|
+
assertEquals((await handle(await secureDocument(tamperedInner, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod))).status, 401);
|
|
102
|
+
const changedAfterSigning = structuredClone(valid);
|
|
103
|
+
changedAfterSigning.signature = {
|
|
104
|
+
type: "RsaSignature2017",
|
|
105
|
+
creator: "https://example.com/keys/1",
|
|
106
|
+
created: "2023-02-24T23:36:38Z",
|
|
107
|
+
signatureValue: "not-a-signature"
|
|
108
|
+
};
|
|
109
|
+
assertEquals((await handle(changedAfterSigning)).status, 401);
|
|
110
|
+
});
|
|
111
|
+
test("handleInbox() enforces compounds after alternate outer auth", async () => {
|
|
112
|
+
const createBody = (object) => ({
|
|
113
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
114
|
+
id: "https://example.com/activities/compound",
|
|
115
|
+
type: "Create",
|
|
116
|
+
actor: rsaPublicKey3.ownerId.href,
|
|
117
|
+
object
|
|
118
|
+
});
|
|
119
|
+
const validInner = structuredClone(map_local_create_note_default.documents.securedInner);
|
|
120
|
+
const unsignedInner = structuredClone(validInner);
|
|
121
|
+
delete unsignedInner.proof;
|
|
122
|
+
for (const authentication of ["http", "ld"]) {
|
|
123
|
+
const sign = async (body) => {
|
|
124
|
+
if (authentication === "http") return await signRequest(requestFor(body), rsaPrivateKey3, rsaPublicKey3.id);
|
|
125
|
+
return requestFor(await signJsonLd(body, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }));
|
|
126
|
+
};
|
|
127
|
+
const acceptedDispatch = { count: 0 };
|
|
128
|
+
assertEquals([(await handle(await sign(createBody(structuredClone(validInner))), acceptedDispatch)).status, acceptedDispatch.count], [202, 1]);
|
|
129
|
+
const rejectedDispatch = { count: 0 };
|
|
130
|
+
assertEquals([(await handle(await sign(createBody(structuredClone(unsignedInner))), rejectedDispatch)).status, rejectedDispatch.count], [401, 0]);
|
|
131
|
+
}
|
|
132
|
+
const proofSetInner = structuredClone(validInner);
|
|
133
|
+
proofSetInner.proof = [proofSetInner.proof];
|
|
134
|
+
const unsupportedDispatch = { count: 0 };
|
|
135
|
+
assertEquals([(await handle(await signRequest(requestFor(createBody(proofSetInner)), rsaPrivateKey3, rsaPublicKey3.id), unsupportedDispatch)).status, unsupportedDispatch.count], [401, 0]);
|
|
136
|
+
});
|
|
137
|
+
test("handleInbox() preserves ordinary top-level proof behavior", async () => {
|
|
138
|
+
assertEquals((await handle(await secureDocument({
|
|
139
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
140
|
+
id: "https://example.com/activities/ordinary",
|
|
141
|
+
type: "Create",
|
|
142
|
+
actor: map_local_create_note_default.keys.outer.controller,
|
|
143
|
+
object: {
|
|
144
|
+
id: "https://example.com/notes/ordinary",
|
|
145
|
+
type: "Note",
|
|
146
|
+
attributedTo: map_local_create_note_default.keys.outer.controller,
|
|
147
|
+
content: "An ordinary note"
|
|
148
|
+
}
|
|
149
|
+
}, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod))).status, 202);
|
|
150
|
+
});
|
|
151
|
+
//#endregion
|
|
152
|
+
export {};
|
|
@@ -10,12 +10,14 @@ import "../std__assert-CxFT0MNt.mjs";
|
|
|
10
10
|
import { t as assertRejects } from "../assert_rejects-wFbSPn1O.mjs";
|
|
11
11
|
import { t as assert } from "../assert-CebeUf0C.mjs";
|
|
12
12
|
import { r as parseAcceptSignature } from "../accept-CPVqki78.mjs";
|
|
13
|
-
import {
|
|
13
|
+
import { a as generateCryptoKeyPair } from "../key-Ccs8cnd7.mjs";
|
|
14
|
+
import { s as signRequest } from "../http-D9urSO2D.mjs";
|
|
14
15
|
import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
|
|
15
|
-
import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-
|
|
16
|
-
import { a as compactJsonLd, p as signJsonLd } from "../ld-
|
|
16
|
+
import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-CzvJvPHK.mjs";
|
|
17
|
+
import { a as compactJsonLd, p as signJsonLd } from "../ld-qup__MAW.mjs";
|
|
18
|
+
import { r as signObject } from "../proof-BDxNVZ3E.mjs";
|
|
17
19
|
import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
|
|
18
|
-
import { Activity, Create, Note, Person, Tombstone } from "@fedify/vocab";
|
|
20
|
+
import { Activity, Create, CryptographicKey, Multikey, Note, Person, Tombstone } from "@fedify/vocab";
|
|
19
21
|
import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
20
22
|
import { FetchError } from "@fedify/vocab-runtime";
|
|
21
23
|
//#region src/federation/handler.test.ts
|
|
@@ -4574,5 +4576,121 @@ test("handleInbox() challenge policy enabled + unverifiedActivityHandler throws
|
|
|
4574
4576
|
assertEquals(response.headers.get("Cache-Control"), "no-store", "Cache-Control: no-store must be set for challenge-response");
|
|
4575
4577
|
assertEquals(response.headers.get("Vary"), "Accept, Signature", "Vary header must include Accept and Signature");
|
|
4576
4578
|
});
|
|
4579
|
+
test("handleInbox() rejects forged key ownership", async () => {
|
|
4580
|
+
const impersonated = "https://example.com/person2";
|
|
4581
|
+
const attackerKeyId = new URL("https://attacker.example/key");
|
|
4582
|
+
const attackerMultikeyId = new URL("https://attacker.example/multikey");
|
|
4583
|
+
const { privateKey, publicKey } = await generateCryptoKeyPair("RSASSA-PKCS1-v1_5");
|
|
4584
|
+
const forgedKeyDocument = await new CryptographicKey({
|
|
4585
|
+
id: attackerKeyId,
|
|
4586
|
+
owner: new URL(impersonated),
|
|
4587
|
+
publicKey
|
|
4588
|
+
}).toJsonLd({ contextLoader: mockDocumentLoader });
|
|
4589
|
+
const ed25519 = await generateCryptoKeyPair("Ed25519");
|
|
4590
|
+
const forgedMultikeyDocument = await new Multikey({
|
|
4591
|
+
id: attackerMultikeyId,
|
|
4592
|
+
controller: new URL(impersonated),
|
|
4593
|
+
publicKey: ed25519.publicKey
|
|
4594
|
+
}).toJsonLd({ contextLoader: mockDocumentLoader });
|
|
4595
|
+
const documentLoader = (resource) => {
|
|
4596
|
+
if (resource === attackerKeyId.href) return Promise.resolve({
|
|
4597
|
+
contextUrl: null,
|
|
4598
|
+
documentUrl: resource,
|
|
4599
|
+
document: forgedKeyDocument
|
|
4600
|
+
});
|
|
4601
|
+
if (resource === attackerMultikeyId.href) return Promise.resolve({
|
|
4602
|
+
contextUrl: null,
|
|
4603
|
+
documentUrl: resource,
|
|
4604
|
+
document: forgedMultikeyDocument
|
|
4605
|
+
});
|
|
4606
|
+
return mockDocumentLoader(resource);
|
|
4607
|
+
};
|
|
4608
|
+
const federation = createFederation({ kv: new MemoryKvStore() });
|
|
4609
|
+
const inboxOptions = {
|
|
4610
|
+
kv: new MemoryKvStore(),
|
|
4611
|
+
kvPrefixes: {
|
|
4612
|
+
activityIdempotence: ["_fedify", "activityIdempotence"],
|
|
4613
|
+
publicKey: ["_fedify", "publicKey"],
|
|
4614
|
+
acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
|
|
4615
|
+
},
|
|
4616
|
+
actorDispatcher: ((_ctx, identifier) => identifier === "someone" ? new Person({ name: "Someone" }) : null),
|
|
4617
|
+
inboxListeners: new ActivityListenerSet(),
|
|
4618
|
+
onNotFound: () => new Response("Not found", { status: 404 }),
|
|
4619
|
+
signatureTimeWindow: { minutes: 5 },
|
|
4620
|
+
skipSignatureVerification: false
|
|
4621
|
+
};
|
|
4622
|
+
const handle = async (request) => {
|
|
4623
|
+
const context = createRequestContext({
|
|
4624
|
+
federation,
|
|
4625
|
+
request,
|
|
4626
|
+
url: new URL(request.url),
|
|
4627
|
+
data: void 0,
|
|
4628
|
+
documentLoader,
|
|
4629
|
+
contextLoader: mockDocumentLoader
|
|
4630
|
+
});
|
|
4631
|
+
return await handleInbox(request, {
|
|
4632
|
+
recipient: null,
|
|
4633
|
+
context,
|
|
4634
|
+
inboxContextFactory(_activity) {
|
|
4635
|
+
return createInboxContext({
|
|
4636
|
+
...context,
|
|
4637
|
+
clone: void 0
|
|
4638
|
+
});
|
|
4639
|
+
},
|
|
4640
|
+
...inboxOptions
|
|
4641
|
+
});
|
|
4642
|
+
};
|
|
4643
|
+
const activity = {
|
|
4644
|
+
"@context": [
|
|
4645
|
+
"https://www.w3.org/ns/activitystreams",
|
|
4646
|
+
"https://w3id.org/identity/v1",
|
|
4647
|
+
"https://w3id.org/security/v1",
|
|
4648
|
+
"https://w3id.org/security/data-integrity/v1"
|
|
4649
|
+
],
|
|
4650
|
+
id: "https://attacker.example/activities/1",
|
|
4651
|
+
type: "Create",
|
|
4652
|
+
actor: impersonated,
|
|
4653
|
+
object: {
|
|
4654
|
+
id: "https://attacker.example/notes/1",
|
|
4655
|
+
type: "Note",
|
|
4656
|
+
attributedTo: impersonated,
|
|
4657
|
+
content: "Hello World!"
|
|
4658
|
+
}
|
|
4659
|
+
};
|
|
4660
|
+
const httpSignedResponse = await handle(await signRequest(new Request("https://example.com/", {
|
|
4661
|
+
method: "POST",
|
|
4662
|
+
body: JSON.stringify(activity)
|
|
4663
|
+
}), privateKey, attackerKeyId));
|
|
4664
|
+
const ldSignedResponse = await handle(new Request("https://example.com/", {
|
|
4665
|
+
method: "POST",
|
|
4666
|
+
body: JSON.stringify(await signJsonLd(activity, privateKey, attackerKeyId, { contextLoader: mockDocumentLoader }))
|
|
4667
|
+
}));
|
|
4668
|
+
const signedObject = await signObject(await Create.fromJsonLd(activity, {
|
|
4669
|
+
documentLoader,
|
|
4670
|
+
contextLoader: mockDocumentLoader
|
|
4671
|
+
}), ed25519.privateKey, attackerMultikeyId, {
|
|
4672
|
+
documentLoader,
|
|
4673
|
+
contextLoader: mockDocumentLoader,
|
|
4674
|
+
context: ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"]
|
|
4675
|
+
});
|
|
4676
|
+
const proofSignedResponse = await handle(new Request("https://example.com/", {
|
|
4677
|
+
method: "POST",
|
|
4678
|
+
body: JSON.stringify(await signedObject.toJsonLd({
|
|
4679
|
+
format: "compact",
|
|
4680
|
+
contextLoader: mockDocumentLoader,
|
|
4681
|
+
context: ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"]
|
|
4682
|
+
}))
|
|
4683
|
+
}));
|
|
4684
|
+
assertEquals([
|
|
4685
|
+
httpSignedResponse.status,
|
|
4686
|
+
ldSignedResponse.status,
|
|
4687
|
+
proofSignedResponse.status
|
|
4688
|
+
], [
|
|
4689
|
+
401,
|
|
4690
|
+
401,
|
|
4691
|
+
401
|
|
4692
|
+
]);
|
|
4693
|
+
assertEquals(await httpSignedResponse.text(), "Failed to verify the request signature.");
|
|
4694
|
+
});
|
|
4577
4695
|
//#endregion
|
|
4578
4696
|
export {};
|
|
@@ -5,8 +5,8 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
|
|
|
5
5
|
import { t as assertEquals } from "../assert_equals-CBlRfA-_.mjs";
|
|
6
6
|
import "../std__assert-CxFT0MNt.mjs";
|
|
7
7
|
import { n as ed25519PrivateKey, r as ed25519PublicKey, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
|
|
8
|
-
import { o as createFederation } from "../middleware-
|
|
9
|
-
import { r as signObject } from "../proof-
|
|
8
|
+
import { o as createFederation } from "../middleware-CzvJvPHK.mjs";
|
|
9
|
+
import { r as signObject } from "../proof-BDxNVZ3E.mjs";
|
|
10
10
|
import { Create, Follow, Person } from "@fedify/vocab";
|
|
11
11
|
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
12
12
|
//#region src/federation/idempotency.test.ts
|
|
@@ -6,7 +6,7 @@ import { t as ManualClockKvStore } from "../kv-DBRWVx3Q.mjs";
|
|
|
6
6
|
import { t as assertEquals } from "../assert_equals-CBlRfA-_.mjs";
|
|
7
7
|
import { t as assertInstanceOf } from "../assert_instance_of-g3WcyYNR.mjs";
|
|
8
8
|
import { t as assert } from "../assert-CebeUf0C.mjs";
|
|
9
|
-
import { t as KvKeyCache } from "../keycache-
|
|
9
|
+
import { t as KvKeyCache } from "../keycache-BUDHB5rY.mjs";
|
|
10
10
|
import { CryptographicKey, Multikey } from "@fedify/vocab";
|
|
11
11
|
import { test } from "@fedify/fixture";
|
|
12
12
|
//#region src/federation/keycache.test.ts
|
|
@@ -14,25 +14,41 @@ test("KvKeyCache.set()", async () => {
|
|
|
14
14
|
const kv = new MemoryKvStore();
|
|
15
15
|
const cache = new KvKeyCache(kv, ["pk"]);
|
|
16
16
|
await cache.set(new URL("https://example.com/key"), new CryptographicKey({ id: new URL("https://example.com/key") }));
|
|
17
|
-
assertEquals(await kv.get([
|
|
17
|
+
assertEquals(await kv.get([
|
|
18
|
+
"pk",
|
|
19
|
+
"2",
|
|
20
|
+
"https://example.com/key"
|
|
21
|
+
]), {
|
|
18
22
|
"@context": "https://w3id.org/security/v1",
|
|
19
23
|
id: "https://example.com/key",
|
|
20
24
|
type: "CryptographicKey"
|
|
21
25
|
});
|
|
22
26
|
await cache.set(new URL("https://example.com/key2"), new Multikey({ id: new URL("https://example.com/key2") }));
|
|
23
|
-
assertEquals(await kv.get([
|
|
27
|
+
assertEquals(await kv.get([
|
|
28
|
+
"pk",
|
|
29
|
+
"2",
|
|
30
|
+
"https://example.com/key2"
|
|
31
|
+
]), {
|
|
24
32
|
"@context": "https://w3id.org/security/multikey/v1",
|
|
25
33
|
id: "https://example.com/key2",
|
|
26
34
|
type: "Multikey"
|
|
27
35
|
});
|
|
28
36
|
await cache.set(new URL("https://example.com/null"), null);
|
|
29
37
|
assert(cache.nullKeys.has("https://example.com/null"));
|
|
30
|
-
assertEquals(await kv.get([
|
|
38
|
+
assertEquals(await kv.get([
|
|
39
|
+
"pk",
|
|
40
|
+
"2",
|
|
41
|
+
"https://example.com/null"
|
|
42
|
+
]), null);
|
|
31
43
|
});
|
|
32
44
|
test("KvKeyCache.get()", async () => {
|
|
33
45
|
const kv = new MemoryKvStore();
|
|
34
46
|
const cache = new KvKeyCache(kv, ["pk"]);
|
|
35
|
-
await kv.set([
|
|
47
|
+
await kv.set([
|
|
48
|
+
"pk",
|
|
49
|
+
"2",
|
|
50
|
+
"https://example.com/key"
|
|
51
|
+
], {
|
|
36
52
|
"@context": "https://w3id.org/security/v1",
|
|
37
53
|
id: "https://example.com/key",
|
|
38
54
|
type: "CryptographicKey"
|
|
@@ -40,7 +56,11 @@ test("KvKeyCache.get()", async () => {
|
|
|
40
56
|
const cryptoKey = await cache.get(new URL("https://example.com/key"));
|
|
41
57
|
assertInstanceOf(cryptoKey, CryptographicKey);
|
|
42
58
|
assertEquals(cryptoKey?.id?.href, "https://example.com/key");
|
|
43
|
-
await kv.set([
|
|
59
|
+
await kv.set([
|
|
60
|
+
"pk",
|
|
61
|
+
"2",
|
|
62
|
+
"https://example.com/key2"
|
|
63
|
+
], {
|
|
44
64
|
"@context": "https://w3id.org/security/multikey/v1",
|
|
45
65
|
id: "https://example.com/key2",
|
|
46
66
|
type: "Multikey"
|
|
@@ -50,7 +70,11 @@ test("KvKeyCache.get()", async () => {
|
|
|
50
70
|
assertEquals(multikey?.id?.href, "https://example.com/key2");
|
|
51
71
|
cache.nullKeys.set("https://example.com/null", Temporal.Now.instant().add(Temporal.Duration.from({ minutes: 10 })));
|
|
52
72
|
assertEquals(await cache.get(new URL("https://example.com/null")), null);
|
|
53
|
-
await kv.set([
|
|
73
|
+
await kv.set([
|
|
74
|
+
"pk",
|
|
75
|
+
"2",
|
|
76
|
+
"https://example.com/null2"
|
|
77
|
+
], null);
|
|
54
78
|
assertEquals(await new KvKeyCache(kv, ["pk"]).get(new URL("https://example.com/null2")), null);
|
|
55
79
|
});
|
|
56
80
|
test("KvKeyCache fetch error metadata", async () => {
|
|
@@ -105,10 +129,18 @@ test("KvKeyCache cached keys expire after keyTtl", async () => {
|
|
|
105
129
|
const cache = new KvKeyCache(kv, ["pk"], { keyTtl: Temporal.Duration.from({ days: 30 }) });
|
|
106
130
|
const keyId = new URL("https://example.com/key");
|
|
107
131
|
await cache.set(keyId, new CryptographicKey({ id: keyId }));
|
|
108
|
-
assert(await kv.get([
|
|
132
|
+
assert(await kv.get([
|
|
133
|
+
"pk",
|
|
134
|
+
"2",
|
|
135
|
+
keyId.href
|
|
136
|
+
]) != null);
|
|
109
137
|
assertInstanceOf(await cache.get(keyId), CryptographicKey);
|
|
110
138
|
kv.advance({ days: 31 });
|
|
111
|
-
assertEquals(await kv.get([
|
|
139
|
+
assertEquals(await kv.get([
|
|
140
|
+
"pk",
|
|
141
|
+
"2",
|
|
142
|
+
keyId.href
|
|
143
|
+
]), void 0);
|
|
112
144
|
assertEquals(await cache.get(keyId), void 0);
|
|
113
145
|
assertEquals(cache.nullKeys.has(keyId.href), false);
|
|
114
146
|
await cache.set(keyId, new CryptographicKey({ id: keyId }));
|
|
@@ -116,5 +148,16 @@ test("KvKeyCache cached keys expire after keyTtl", async () => {
|
|
|
116
148
|
assertInstanceOf(refetched, CryptographicKey);
|
|
117
149
|
assertEquals(refetched.id?.href, keyId.href);
|
|
118
150
|
});
|
|
151
|
+
test("KvKeyCache.get() ignores entries from an earlier generation", async () => {
|
|
152
|
+
const kv = new MemoryKvStore();
|
|
153
|
+
const cache = new KvKeyCache(kv, ["pk"]);
|
|
154
|
+
await kv.set(["pk", "https://example.com/key"], {
|
|
155
|
+
"@context": "https://w3id.org/security/v1",
|
|
156
|
+
id: "https://example.com/key",
|
|
157
|
+
owner: "https://example.com/impersonated",
|
|
158
|
+
type: "CryptographicKey"
|
|
159
|
+
});
|
|
160
|
+
assertEquals(await cache.get(new URL("https://example.com/key")), void 0);
|
|
161
|
+
});
|
|
119
162
|
//#endregion
|
|
120
163
|
export {};
|
|
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-CBlRfA-_.mjs";
|
|
5
5
|
import "../std__assert-CxFT0MNt.mjs";
|
|
6
6
|
import { t as assertRejects } from "../assert_rejects-wFbSPn1O.mjs";
|
|
7
|
-
import { _ as recordKeyLookup, a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, m as recordDocumentFetch, p as recordDocumentCache, r as getFederationMetrics, t as classifyFetchError, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "../metrics-
|
|
7
|
+
import { _ as recordKeyLookup, a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, m as recordDocumentFetch, p as recordDocumentCache, r as getFederationMetrics, t as classifyFetchError, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "../metrics-CISXCvB1.mjs";
|
|
8
8
|
import { createTestMeterProvider, test } from "@fedify/fixture";
|
|
9
9
|
import { FetchError } from "@fedify/vocab-runtime";
|
|
10
10
|
import { DataPointType, MeterProvider, MetricReader } from "@opentelemetry/sdk-metrics";
|