@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,12 +1,12 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-
|
|
6
|
-
import {
|
|
4
|
+
import { n as version, t as name } from "./deno-CABhzCb_.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-CISXCvB1.mjs";
|
|
6
|
+
import { r as fetchKey, s as validateCryptoKey } from "./key-Ccs8cnd7.mjs";
|
|
7
7
|
import { n as preloadedOnlyDocumentLoader } from "./public-audience-Cc2YJ3ip.mjs";
|
|
8
8
|
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-B5Ne7JoL.mjs";
|
|
9
|
-
import { c as getNormalizationContextLoader } from "./ld-
|
|
9
|
+
import { c as getNormalizationContextLoader } from "./ld-qup__MAW.mjs";
|
|
10
10
|
import { getLogger } from "@logtape/logtape";
|
|
11
11
|
import { Activity, DataIntegrityProof, Multikey, getTypeId } from "@fedify/vocab";
|
|
12
12
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -164,7 +164,31 @@ async function signObject(object, privateKey, keyId, options = {}) {
|
|
|
164
164
|
async function verifyProof(jsonLd, proof, options = {}) {
|
|
165
165
|
return await verifyProofWithMessageDigestCache(jsonLd, proof, options);
|
|
166
166
|
}
|
|
167
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Verifies the one direct literal proof on a map-local secured document.
|
|
169
|
+
*
|
|
170
|
+
* Unlike {@link verifyProof}, this removes only the literal `proof` member
|
|
171
|
+
* from the message digest. JSON-LD aliases remain part of the signed input,
|
|
172
|
+
* and the document's received `@context` is not replaced by the proof
|
|
173
|
+
* configuration context.
|
|
174
|
+
*
|
|
175
|
+
* @internal
|
|
176
|
+
*/
|
|
177
|
+
async function verifyMapLocalProof(jsonLd, options = {}) {
|
|
178
|
+
if (!isJsonLdNode(jsonLd) || !globalThis.Object.hasOwn(jsonLd, "proof") || !isJsonLdNode(jsonLd.proof)) return null;
|
|
179
|
+
const proofContextLoader = getNormalizationContextLoader(preloadedOnlyDocumentLoader);
|
|
180
|
+
try {
|
|
181
|
+
const [candidate] = await parseRawProofCandidates(jsonLd, [jsonLd.proof], options, proofContextLoader);
|
|
182
|
+
if (candidate.proof == null) return null;
|
|
183
|
+
return await verifyProofWithMessageDigestCache(jsonLd, candidate.proof, options, {
|
|
184
|
+
proofContextLoader,
|
|
185
|
+
proofPropertyMode: "literal"
|
|
186
|
+
}, candidate);
|
|
187
|
+
} catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function verifyProofWithMessageDigestCache(jsonLd, proof, options, messageDigestCache = {}, rawProofCandidate, keyIdBoundByCaller = false) {
|
|
168
192
|
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
|
|
169
193
|
const start = performance.now();
|
|
170
194
|
let verified = false;
|
|
@@ -176,7 +200,7 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
|
|
|
176
200
|
if (proof.proofValue != null) span.setAttribute("object_integrity_proofs.signature", encodeHex(proof.proofValue));
|
|
177
201
|
}
|
|
178
202
|
try {
|
|
179
|
-
const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate);
|
|
203
|
+
const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
180
204
|
if (key == null) span.setStatus({ code: SpanStatusCode.ERROR });
|
|
181
205
|
else verified = true;
|
|
182
206
|
return key;
|
|
@@ -234,10 +258,11 @@ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoa
|
|
|
234
258
|
async function getProofPropertyNames(jsonLd, documentLoader = preloadedOnlyDocumentLoader) {
|
|
235
259
|
return await getJsonLdPropertyNames(jsonLd, SECURITY_PROOF, ["proof", SECURITY_PROOF], documentLoader) ?? /* @__PURE__ */ new Set(["proof", SECURITY_PROOF]);
|
|
236
260
|
}
|
|
237
|
-
async function createProofMessageDigests(jsonLd, proofContextLoader, context) {
|
|
261
|
+
async function createProofMessageDigests(jsonLd, proofContextLoader, context, proofPropertyMode = "jsonLd") {
|
|
238
262
|
const msg = { ...jsonLd };
|
|
239
|
-
|
|
240
|
-
|
|
263
|
+
if (proofPropertyMode === "literal") delete msg.proof;
|
|
264
|
+
else for (const property of await getProofPropertyNames(msg, proofContextLoader)) delete msg[property];
|
|
265
|
+
if (proofPropertyMode === "jsonLd" && context != null) msg["@context"] = structuredClone(context);
|
|
241
266
|
const encoder = new TextEncoder();
|
|
242
267
|
const digest = async (value) => {
|
|
243
268
|
const bytes = encoder.encode(serialize(value));
|
|
@@ -493,12 +518,15 @@ async function hasValidProofOptions(proofConfig, options, documentLoader) {
|
|
|
493
518
|
if (previousProof == null || previousProof.present && typeof previousProof.value !== "string" && (!Array.isArray(previousProof.value) || previousProof.value.some((item) => typeof item !== "string"))) return false;
|
|
494
519
|
return true;
|
|
495
520
|
}
|
|
496
|
-
async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate) {
|
|
521
|
+
async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller = false) {
|
|
497
522
|
if (!isJsonLdNode(jsonLd) || proof.cryptosuite !== "eddsa-jcs-2022" || proof.verificationMethodId == null || proof.proofPurpose !== "assertionMethod" || proof.proofValue == null || proof.created == null) return null;
|
|
498
523
|
const proofContextLoader = messageDigestCache.proofContextLoader ?? preloadedOnlyDocumentLoader;
|
|
499
524
|
const proofConfiguration = await createProofConfiguration(jsonLd, proof, options, proofContextLoader, rawProofCandidate);
|
|
500
525
|
if (proofConfiguration == null || !await hasValidProofOptions(proofConfiguration.value, options, proofContextLoader)) return null;
|
|
501
|
-
const publicKeyPromise = measureSignatureKeyFetch(options.meterProvider, "object_integrity", () => fetchKey(proof.verificationMethodId, Multikey,
|
|
526
|
+
const publicKeyPromise = measureSignatureKeyFetch(options.meterProvider, "object_integrity", () => fetchKey(proof.verificationMethodId, Multikey, {
|
|
527
|
+
...options,
|
|
528
|
+
keyIdBoundByCaller
|
|
529
|
+
}));
|
|
502
530
|
const proofBytes = new TextEncoder().encode(serialize(proofConfiguration.value));
|
|
503
531
|
const proofDigest = await crypto.subtle.digest("SHA-256", proofBytes);
|
|
504
532
|
let fetchedKey;
|
|
@@ -532,7 +560,7 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
|
|
|
532
560
|
get: () => Promise.resolve(void 0),
|
|
533
561
|
set: async (keyId, key) => await options.keyCache?.set(keyId, key)
|
|
534
562
|
}
|
|
535
|
-
}, messageDigestCache, rawProofCandidate);
|
|
563
|
+
}, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
536
564
|
}
|
|
537
565
|
logger.debug("The fetched key (verificationMethod) for the proof is not a valid Ed25519 key:\n{keyId}", {
|
|
538
566
|
proof,
|
|
@@ -551,13 +579,15 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
|
|
|
551
579
|
const messageDigestValues = messageDigestCache.values ??= /* @__PURE__ */ new Map();
|
|
552
580
|
let messageDigestsPromise = messageDigestValues.get(messageDigestKey);
|
|
553
581
|
if (messageDigestsPromise == null) {
|
|
554
|
-
messageDigestsPromise = createProofMessageDigests(jsonLd, messageDigestCache.proofContextLoader, proofConfiguration.context);
|
|
582
|
+
messageDigestsPromise = createProofMessageDigests(jsonLd, messageDigestCache.proofContextLoader, proofConfiguration.context, messageDigestCache.proofPropertyMode);
|
|
555
583
|
messageDigestValues.set(messageDigestKey, messageDigestsPromise);
|
|
556
584
|
}
|
|
557
585
|
const messageDigests = await messageDigestsPromise;
|
|
558
586
|
if (await verifyCandidate(messageDigests.onWire)) return publicKey;
|
|
559
|
-
|
|
560
|
-
|
|
587
|
+
if (messageDigestCache.proofPropertyMode !== "literal") {
|
|
588
|
+
const normalizedDigest = await messageDigests.normalized();
|
|
589
|
+
if (normalizedDigest != null && await verifyCandidate(normalizedDigest)) return publicKey;
|
|
590
|
+
}
|
|
561
591
|
if (fetchedKey.cached) {
|
|
562
592
|
logger.debug("Failed to verify the proof with the cached key {keyId}; retrying with the freshly fetched key...", {
|
|
563
593
|
keyId: proof.verificationMethodId.href,
|
|
@@ -569,7 +599,7 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
|
|
|
569
599
|
get: () => Promise.resolve(void 0),
|
|
570
600
|
set: async (keyId, key) => await options.keyCache?.set(keyId, key)
|
|
571
601
|
}
|
|
572
|
-
}, messageDigestCache, rawProofCandidate);
|
|
602
|
+
}, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
573
603
|
}
|
|
574
604
|
logger.debug("Failed to verify the proof with the fetched key {keyId}:\n{proof}", {
|
|
575
605
|
keyId: proof.verificationMethodId.href,
|
|
@@ -649,59 +679,55 @@ async function expandPortableObjectRoot(jsonLd, contextLoader) {
|
|
|
649
679
|
}
|
|
650
680
|
};
|
|
651
681
|
}
|
|
652
|
-
|
|
653
|
-
* Verifies the FEP-ef61 Object Integrity Proof policy for a portable object.
|
|
654
|
-
*
|
|
655
|
-
* This applies the FEP-2277 core-type classification to the top-level JSON-LD
|
|
656
|
-
* node. Portable actors, activities, and objects require proofs. A portable
|
|
657
|
-
* collection without a proof is reported separately so a caller can apply a
|
|
658
|
-
* gateway trust policy. Embedded portable objects are not traversed.
|
|
659
|
-
*
|
|
660
|
-
* Every proof must use a DID URL whose DID matches the portable object's
|
|
661
|
-
* authority, and every proof must pass {@link verifyProof}.
|
|
662
|
-
*
|
|
663
|
-
* @param jsonLd The JSON-LD document to verify.
|
|
664
|
-
* @param options Additional options. See also
|
|
665
|
-
* {@link VerifyPortableObjectProofOptions}.
|
|
666
|
-
* @returns The detailed portable proof-policy result.
|
|
667
|
-
* @throws {TypeError} If the input is not a single JSON-LD object or has a
|
|
668
|
-
* malformed portable ID.
|
|
669
|
-
* @since 2.4.0
|
|
670
|
-
*/
|
|
671
|
-
async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
682
|
+
async function preparePortableObjectProof(jsonLd, options) {
|
|
672
683
|
if (isJsonLdNode(jsonLd) && typeof jsonLd["@id"] === "string" && !PORTABLE_OBJECT_ID_PATTERN.test(jsonLd["@id"])) return {
|
|
673
|
-
|
|
674
|
-
|
|
684
|
+
prepared: false,
|
|
685
|
+
result: {
|
|
686
|
+
verified: false,
|
|
687
|
+
reason: { type: "notPortableObject" }
|
|
688
|
+
}
|
|
675
689
|
};
|
|
676
690
|
const { root, proofContextLoader } = await expandPortableObjectRoot(jsonLd, options.contextLoader);
|
|
677
691
|
const id = root["@id"];
|
|
678
692
|
if (typeof id !== "string" || !PORTABLE_OBJECT_ID_PATTERN.test(id)) return {
|
|
679
|
-
|
|
680
|
-
|
|
693
|
+
prepared: false,
|
|
694
|
+
result: {
|
|
695
|
+
verified: false,
|
|
696
|
+
reason: { type: "notPortableObject" }
|
|
697
|
+
}
|
|
681
698
|
};
|
|
682
699
|
const objectId = parseIri(id);
|
|
683
700
|
getFe34Origin(objectId);
|
|
684
701
|
const objectType = classifyFep2277CoreType(root);
|
|
685
702
|
if (objectType === "verificationMethod" || objectType === "publicKey" || objectType === "link") return {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
703
|
+
prepared: false,
|
|
704
|
+
result: {
|
|
705
|
+
verified: false,
|
|
706
|
+
reason: {
|
|
707
|
+
type: "unsupportedObjectType",
|
|
708
|
+
objectType
|
|
709
|
+
}
|
|
690
710
|
}
|
|
691
711
|
};
|
|
692
712
|
const proofValues = root[SECURITY_PROOF];
|
|
693
|
-
if (proofValues == null || Array.isArray(proofValues) && proofValues.length < 1) return
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
713
|
+
if (proofValues == null || Array.isArray(proofValues) && proofValues.length < 1) return {
|
|
714
|
+
prepared: false,
|
|
715
|
+
result: objectType === "collection" ? {
|
|
716
|
+
verified: false,
|
|
717
|
+
reason: { type: "unsecuredCollection" }
|
|
718
|
+
} : {
|
|
719
|
+
verified: false,
|
|
720
|
+
reason: { type: "missingProof" }
|
|
721
|
+
}
|
|
699
722
|
};
|
|
700
723
|
if (!Array.isArray(proofValues)) return {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
724
|
+
prepared: false,
|
|
725
|
+
result: {
|
|
726
|
+
verified: false,
|
|
727
|
+
reason: {
|
|
728
|
+
type: "invalidProof",
|
|
729
|
+
proofIndex: 0
|
|
730
|
+
}
|
|
705
731
|
}
|
|
706
732
|
};
|
|
707
733
|
const rawProofValues = isJsonLdNode(jsonLd) ? await getRawProofValues(jsonLd, proofContextLoader) : [];
|
|
@@ -709,10 +735,13 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
|
709
735
|
for (let proofIndex = 0; proofIndex < proofValues.length; proofIndex++) {
|
|
710
736
|
const proofValue = proofValues[proofIndex];
|
|
711
737
|
if (!hasValidPortableProofShape(proofValue)) return {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
738
|
+
prepared: false,
|
|
739
|
+
result: {
|
|
740
|
+
verified: false,
|
|
741
|
+
reason: {
|
|
742
|
+
type: "invalidProof",
|
|
743
|
+
proofIndex
|
|
744
|
+
}
|
|
716
745
|
}
|
|
717
746
|
};
|
|
718
747
|
let proof;
|
|
@@ -720,10 +749,13 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
|
720
749
|
proof = await DataIntegrityProof.fromJsonLd(proofValue, options);
|
|
721
750
|
} catch {
|
|
722
751
|
return {
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
752
|
+
prepared: false,
|
|
753
|
+
result: {
|
|
754
|
+
verified: false,
|
|
755
|
+
reason: {
|
|
756
|
+
type: "invalidProof",
|
|
757
|
+
proofIndex
|
|
758
|
+
}
|
|
727
759
|
}
|
|
728
760
|
};
|
|
729
761
|
}
|
|
@@ -732,18 +764,24 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
|
732
764
|
for (let proofIndex = 0; proofIndex < proofs.length; proofIndex++) {
|
|
733
765
|
const verificationMethod = proofs[proofIndex].verificationMethodId;
|
|
734
766
|
if (verificationMethod == null) return {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
767
|
+
prepared: false,
|
|
768
|
+
result: {
|
|
769
|
+
verified: false,
|
|
770
|
+
reason: {
|
|
771
|
+
type: "invalidProof",
|
|
772
|
+
proofIndex
|
|
773
|
+
}
|
|
739
774
|
}
|
|
740
775
|
};
|
|
741
776
|
if (verificationMethod.protocol !== "did:") return {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
777
|
+
prepared: false,
|
|
778
|
+
result: {
|
|
779
|
+
verified: false,
|
|
780
|
+
reason: {
|
|
781
|
+
type: "unsupportedVerificationMethod",
|
|
782
|
+
proofIndex,
|
|
783
|
+
verificationMethod
|
|
784
|
+
}
|
|
747
785
|
}
|
|
748
786
|
};
|
|
749
787
|
try {
|
|
@@ -751,24 +789,89 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
|
751
789
|
} catch (error) {
|
|
752
790
|
if (!(error instanceof TypeError)) throw error;
|
|
753
791
|
return {
|
|
792
|
+
prepared: false,
|
|
793
|
+
result: {
|
|
794
|
+
verified: false,
|
|
795
|
+
reason: {
|
|
796
|
+
type: "unsupportedVerificationMethod",
|
|
797
|
+
proofIndex,
|
|
798
|
+
verificationMethod
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
if (!haveSameFe34Origin(objectId, verificationMethod)) return {
|
|
804
|
+
prepared: false,
|
|
805
|
+
result: {
|
|
754
806
|
verified: false,
|
|
755
807
|
reason: {
|
|
756
|
-
type: "
|
|
808
|
+
type: "verificationMethodMismatch",
|
|
757
809
|
proofIndex,
|
|
810
|
+
objectId,
|
|
758
811
|
verificationMethod
|
|
759
812
|
}
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
if (!haveSameFe34Origin(objectId, verificationMethod)) return {
|
|
763
|
-
verified: false,
|
|
764
|
-
reason: {
|
|
765
|
-
type: "verificationMethodMismatch",
|
|
766
|
-
proofIndex,
|
|
767
|
-
objectId,
|
|
768
|
-
verificationMethod
|
|
769
813
|
}
|
|
770
814
|
};
|
|
771
815
|
}
|
|
816
|
+
return {
|
|
817
|
+
prepared: true,
|
|
818
|
+
objectId,
|
|
819
|
+
proofs,
|
|
820
|
+
rawProofValues,
|
|
821
|
+
proofContextLoader
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Applies portable-object proof policy to one map-local cryptographic result.
|
|
826
|
+
*
|
|
827
|
+
* @internal
|
|
828
|
+
*/
|
|
829
|
+
async function verifyPortableObjectProofPolicy(jsonLd, key, options = {}) {
|
|
830
|
+
const policyOptions = {
|
|
831
|
+
...options,
|
|
832
|
+
contextLoader: preloadedOnlyDocumentLoader
|
|
833
|
+
};
|
|
834
|
+
const prepared = await preparePortableObjectProof(jsonLd, policyOptions);
|
|
835
|
+
if (!prepared.prepared) return prepared.result;
|
|
836
|
+
const policyProof = prepared.proofs[0];
|
|
837
|
+
const literalProof = isJsonLdNode(jsonLd) && isJsonLdNode(jsonLd.proof) ? (await parseRawProofCandidates(jsonLd, [jsonLd.proof], policyOptions, prepared.proofContextLoader))[0]?.proof : null;
|
|
838
|
+
const verificationMethod = policyProof?.verificationMethodId;
|
|
839
|
+
if (prepared.proofs.length !== 1 || key == null || policyProof == null || literalProof == null || !sameProof(policyProof, literalProof) || verificationMethod == null || key.id?.href !== verificationMethod.href) return {
|
|
840
|
+
verified: false,
|
|
841
|
+
reason: {
|
|
842
|
+
type: "invalidProof",
|
|
843
|
+
proofIndex: prepared.proofs.length > 1 ? 1 : 0
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
return {
|
|
847
|
+
verified: true,
|
|
848
|
+
keys: [key],
|
|
849
|
+
objectId: formatIri(prepared.objectId)
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Verifies the FEP-ef61 Object Integrity Proof policy for a portable object.
|
|
854
|
+
*
|
|
855
|
+
* This applies the FEP-2277 core-type classification to the top-level JSON-LD
|
|
856
|
+
* node. Portable actors, activities, and objects require proofs. A portable
|
|
857
|
+
* collection without a proof is reported separately so a caller can apply a
|
|
858
|
+
* gateway trust policy. Embedded portable objects are not traversed.
|
|
859
|
+
*
|
|
860
|
+
* Every proof must use a DID URL whose DID matches the portable object's
|
|
861
|
+
* authority, and every proof must pass {@link verifyProof}.
|
|
862
|
+
*
|
|
863
|
+
* @param jsonLd The JSON-LD document to verify.
|
|
864
|
+
* @param options Additional options. See also
|
|
865
|
+
* {@link VerifyPortableObjectProofOptions}.
|
|
866
|
+
* @returns The detailed portable proof-policy result.
|
|
867
|
+
* @throws {TypeError} If the input is not a single JSON-LD object or has a
|
|
868
|
+
* malformed portable ID.
|
|
869
|
+
* @since 2.4.0
|
|
870
|
+
*/
|
|
871
|
+
async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
872
|
+
const prepared = await preparePortableObjectProof(jsonLd, options);
|
|
873
|
+
if (!prepared.prepared) return prepared.result;
|
|
874
|
+
const { proofs, rawProofValues, proofContextLoader } = prepared;
|
|
772
875
|
const keys = [];
|
|
773
876
|
const rawProofCandidatePool = {
|
|
774
877
|
candidates: await parseRawProofCandidates(jsonLd, rawProofValues, options, proofContextLoader),
|
|
@@ -784,7 +887,7 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
|
784
887
|
proofIndex
|
|
785
888
|
}
|
|
786
889
|
};
|
|
787
|
-
const key = await verifyProofWithMessageDigestCache(jsonLd, proofs[proofIndex], options, messageDigestCache, rawProofCandidate);
|
|
890
|
+
const key = await verifyProofWithMessageDigestCache(jsonLd, proofs[proofIndex], options, messageDigestCache, rawProofCandidate, true);
|
|
788
891
|
if (key == null) return {
|
|
789
892
|
verified: false,
|
|
790
893
|
reason: {
|
|
@@ -887,4 +990,4 @@ function hasCryptographicOrigin(iri) {
|
|
|
887
990
|
return /^did:/i.test(iri) || /^ap(?:\+ef61)?:\/\//i.test(iri);
|
|
888
991
|
}
|
|
889
992
|
//#endregion
|
|
890
|
-
export {
|
|
993
|
+
export { verifyObject as a, verifyProof as c, verifyMapLocalProof as i, hasProofLike as n, verifyPortableObjectProof as o, signObject as r, verifyPortableObjectProofPolicy as s, createProof as t };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { n as getDurationMs, r as getFederationMetrics } from "./metrics-
|
|
6
|
-
import { n as doubleKnock } from "./http-
|
|
4
|
+
import { n as version, t as name } from "./deno-CABhzCb_.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics } from "./metrics-CISXCvB1.mjs";
|
|
6
|
+
import { n as doubleKnock } from "./http-D9urSO2D.mjs";
|
|
7
7
|
import { getLogger } from "@logtape/logtape";
|
|
8
8
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
9
|
-
import { FetchError } from "@fedify/vocab-runtime";
|
|
9
|
+
import { FetchError, UrlError, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
10
10
|
//#region src/federation/send.ts
|
|
11
11
|
/**
|
|
12
12
|
* Extracts the inbox URLs from recipients.
|
|
@@ -112,7 +112,7 @@ async function readLimitedResponseBody(response, maxBytes) {
|
|
|
112
112
|
if (truncated) result += "… (truncated)";
|
|
113
113
|
return result;
|
|
114
114
|
}
|
|
115
|
-
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, headers, specDeterminer, meterProvider, tracerProvider }, span) {
|
|
115
|
+
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span) {
|
|
116
116
|
const logger = getLogger([
|
|
117
117
|
"fedify",
|
|
118
118
|
"federation",
|
|
@@ -121,12 +121,25 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
121
121
|
const federationMetrics = getFederationMetrics(meterProvider);
|
|
122
122
|
const started = performance.now();
|
|
123
123
|
let deliverySuccess = false;
|
|
124
|
+
async function validateUrl(url) {
|
|
125
|
+
if (!allowPrivateAddress) try {
|
|
126
|
+
await validatePublicUrl(url);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error instanceof UrlError) logger.error("Disallowed private URL: {url}", {
|
|
129
|
+
url,
|
|
130
|
+
error
|
|
131
|
+
});
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
await validateUrl(inbox.href);
|
|
124
136
|
headers = new Headers(headers);
|
|
125
137
|
headers.set("Content-Type", "application/activity+json");
|
|
126
138
|
const request = new Request(inbox, {
|
|
127
139
|
method: "POST",
|
|
128
140
|
headers,
|
|
129
|
-
body: JSON.stringify(activity)
|
|
141
|
+
body: JSON.stringify(activity),
|
|
142
|
+
redirect: "manual"
|
|
130
143
|
});
|
|
131
144
|
let rsaKey = null;
|
|
132
145
|
for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
@@ -142,19 +155,20 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
142
155
|
});
|
|
143
156
|
let response;
|
|
144
157
|
try {
|
|
145
|
-
response = rsaKey == null ? await
|
|
158
|
+
response = rsaKey == null ? await fetchWithValidatedRedirects(request, validateUrl) : await doubleKnock(request, rsaKey, {
|
|
146
159
|
tracerProvider,
|
|
147
|
-
specDeterminer
|
|
160
|
+
specDeterminer,
|
|
161
|
+
validateRedirect: validateUrl
|
|
148
162
|
});
|
|
149
163
|
} catch (error) {
|
|
150
|
-
const
|
|
164
|
+
const failure = error instanceof FetchError || error instanceof UrlError ? error : createFetchError(inbox.href, error);
|
|
151
165
|
logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
|
|
152
166
|
activityId,
|
|
153
167
|
inbox: inbox.href,
|
|
154
|
-
error:
|
|
168
|
+
error: failure
|
|
155
169
|
});
|
|
156
170
|
federationMetrics.recordDelivery(inbox, getDurationMs(started), false, activityType);
|
|
157
|
-
throw
|
|
171
|
+
throw failure;
|
|
158
172
|
}
|
|
159
173
|
try {
|
|
160
174
|
if (!response.ok) {
|
|
@@ -191,6 +205,49 @@ function createFetchError(url, cause) {
|
|
|
191
205
|
error.cause = cause;
|
|
192
206
|
return error;
|
|
193
207
|
}
|
|
208
|
+
async function fetchWithValidatedRedirects(request, validateUrl) {
|
|
209
|
+
let body = await request.clone().text();
|
|
210
|
+
for (let redirects = 0;; redirects++) {
|
|
211
|
+
const response = await fetch(request, { redirect: "manual" });
|
|
212
|
+
const location = response.headers.get("Location");
|
|
213
|
+
if (![
|
|
214
|
+
301,
|
|
215
|
+
302,
|
|
216
|
+
303,
|
|
217
|
+
307,
|
|
218
|
+
308
|
|
219
|
+
].includes(response.status) || location == null) return response;
|
|
220
|
+
await response.body?.cancel();
|
|
221
|
+
if (redirects >= 20) throw new TypeError("Too many redirects");
|
|
222
|
+
const url = new URL(location, request.url);
|
|
223
|
+
await validateUrl(url.href);
|
|
224
|
+
const headers = new Headers(request.headers);
|
|
225
|
+
if (url.origin !== new URL(request.url).origin) {
|
|
226
|
+
headers.delete("Authorization");
|
|
227
|
+
headers.delete("Proxy-Authorization");
|
|
228
|
+
headers.delete("Cookie");
|
|
229
|
+
headers.delete("Host");
|
|
230
|
+
}
|
|
231
|
+
let method = request.method;
|
|
232
|
+
if ((response.status === 301 || response.status === 302) && method === "POST" || response.status === 303 && method !== "GET" && method !== "HEAD") {
|
|
233
|
+
method = "GET";
|
|
234
|
+
body = void 0;
|
|
235
|
+
for (const name of [
|
|
236
|
+
"Content-Encoding",
|
|
237
|
+
"Content-Language",
|
|
238
|
+
"Content-Length",
|
|
239
|
+
"Content-Location",
|
|
240
|
+
"Content-Type"
|
|
241
|
+
]) headers.delete(name);
|
|
242
|
+
}
|
|
243
|
+
request = new Request(url, {
|
|
244
|
+
method,
|
|
245
|
+
headers,
|
|
246
|
+
body,
|
|
247
|
+
redirect: "manual"
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
194
251
|
/**
|
|
195
252
|
* An error that is thrown when an activity fails to send to a remote inbox.
|
|
196
253
|
* It contains structured information about the failure, including the HTTP
|