@fedify/fedify 2.4.0-dev.2065 → 2.4.0-dev.2071
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-CMkrnYbY.mjs → builder-DVQ0yhKJ.mjs} +6 -2
- 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-Dj3DCsoj.mjs → compound-proof-KmXDfWFp.mjs} +1 -1
- package/dist/{context-b36TpPoG.d.cts → context-DijrgHDe.d.cts} +45 -2
- package/dist/{context-D_gg4M0n.d.ts → context-DsB9kF6q.d.ts} +45 -2
- package/dist/{deno-BuRFI7wk.mjs → deno-B-NtYZVW.mjs} +1 -1
- package/dist/{docloader-_aSNLA90.mjs → docloader-C6yOVoaF.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +4 -4
- package/dist/federation/handler-compound-proof.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +8 -8
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +3 -3
- package/dist/federation/mod.d.ts +3 -3
- package/dist/federation/mod.js +1 -1
- package/dist/federation/outgoing-proof.test.mjs +6 -6
- package/dist/federation/portable-gateway-keys.test.d.mts +2 -0
- package/dist/federation/portable-gateway-keys.test.mjs +363 -0
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-Coi_bg12.cjs → federation-CUiHc7dP.cjs} +182 -76
- package/dist/http-BoWBwC0g.js +5087 -0
- package/dist/{http-BGShIsd6.cjs → http-CuLWIJdf.cjs} +2684 -48
- package/dist/{http-f1qaBtOh.mjs → http-DKWi8RjI.mjs} +20 -5
- package/dist/{http-65PYi9jY.d.ts → http-n2v6DPxu.d.ts} +52 -2
- package/dist/{http-CP-7OdSq.d.cts → http-xlG-jRdB.d.cts} +52 -2
- package/dist/{key-BxHsA1s9.mjs → key-lfcx_NaE.mjs} +147 -7
- package/dist/{kv-cache-Cgj-kpQS.js → kv-cache-BdnmnAVc.js} +1 -1
- package/dist/{kv-cache-CY6pDNoS.cjs → kv-cache-DE8kSNQU.cjs} +1 -1
- package/dist/{kv-cache-C9IY-FMN.mjs → kv-cache-d8EjO29d.mjs} +1 -1
- package/dist/{ld-CqJq5erX.mjs → ld-jyjdrWjG.mjs} +3 -3
- package/dist/{metrics-DOqknlZd.mjs → metrics-B5Rei-HC.mjs} +1 -1
- package/dist/{middleware-1h_LgwXC.js → middleware-CAObohey.js} +149 -43
- package/dist/{middleware-s-AZzx4r.mjs → middleware-CPx4ZWwt.mjs} +155 -53
- package/dist/{middleware-VOCZN3SO.mjs → middleware-CdVnCDXD.mjs} +1 -1
- package/dist/{mod-Dhm7SqyE.d.ts → mod-C9WSpZWm.d.ts} +1 -1
- package/dist/{mod-C0BYc0OU.d.ts → mod-CVZrXSdm.d.ts} +1 -1
- package/dist/{mod-BhMvsgdF.d.ts → mod-DMkflQdF.d.ts} +1 -1
- package/dist/{mod-Bfenkg_c.d.cts → mod-DTAaBwiY.d.cts} +1 -1
- package/dist/{mod-pLbeNwcC.d.cts → mod-HDSvS-ww.d.cts} +1 -1
- package/dist/{mod-CyiSjRkk.d.cts → mod-xGFCHqq_.d.cts} +1 -1
- package/dist/mod.cjs +18 -18
- package/dist/mod.d.cts +7 -7
- package/dist/mod.d.ts +7 -7
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/owner-1TFakcTF.js +215 -0
- package/dist/owner-BXufwAzm.cjs +225 -0
- package/dist/{owner-D_F22lfM.mjs → owner-ByqNQVaa.mjs} +45 -2
- package/dist/portable-key-Cw_ow5vn.mjs +194 -0
- package/dist/{proof-B03JXhrk.mjs → proof-B6gwdhN5.mjs} +5 -5
- package/dist/{send-NNkfKtVN.mjs → send-DsK7xevO.mjs} +3 -3
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +2 -2
- package/dist/sig/compound-proof.test.mjs +3 -3
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +16 -16
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +3 -3
- package/dist/sig/portable-key.test.d.mts +2 -0
- package/dist/sig/portable-key.test.mjs +275 -0
- package/dist/sig/proof.test.mjs +4 -4
- package/dist/{sig-Bb20Pyrd.js → sig-BR_Z6mpY.js} +2 -2
- package/dist/{sig-B1E2uyhQ.cjs → sig-TTn9wYrE.cjs} +4 -4
- package/dist/{temporal-VVVSuruq.mjs → temporal-Bw5jtrBY.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +43 -0
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.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/http-z5n7chWw.js +0 -2647
- package/dist/owner-BLBWXCNt.js +0 -2276
- package/dist/owner-D8ZpRusc.cjs +0 -2446
|
@@ -2,10 +2,10 @@ import { Temporal } from "temporal-polyfill";
|
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
3
|
import { t as __exportAll } from "./rolldown-runtime-BC-Eo5Mj.js";
|
|
4
4
|
import { r as getDefaultActivityTransformers } from "./transformers-DAtXl_Kc.js";
|
|
5
|
-
import {
|
|
5
|
+
import { $ as recordCollectionPageItems, C as hasSignature, D as signJsonLd, E as isInvalidUrlTypeError, H as hasPortableActor, J as getRemoteHost, K as getDurationMs, L as importJwk, M as exportJwk, O as verifyCompactJsonLd, Q as recordCollectionDispatchDuration, R as validateCryptoKey, S as getNormalizationContextLoader, T as isClearlyMalformedContextReference, U as isCompatibleKeyId, V as getGatewayKeyBase, W as isPortableUri, X as isAbortError, Y as instrumentDocumentLoader, Z as recordCircuitBreakerStateChange, _ as assertSafeJsonLd, a as verifyRequestDetailed, at as recordOutboxActivity, c as hasProofLike, ct as registerQueueDepthGauge, d as verifyObject, et as recordCollectionRequest, f as verifyPortableObjectProof, g as InvalidContextReferenceError, ht as version, i as verifyRequest, it as recordInboxActivity, j as wrapContextLoaderForJsonLd, l as signObject, lt as normalizeOutgoingActivityJsonLd, mt as name, n as parseRfc9421SignatureInput, ot as recordOutboxEnqueue, p as verifyPortableObjectProofPolicy, q as getFederationMetrics, rt as recordFanoutRecipients, st as recordWebFingerHandle, t as doubleKnock, tt as recordCollectionTotalItems, u as verifyMapLocalProof, ut as formatAcceptSignature, w as hasSignatureLike, x as detachSignature, y as compactJsonLd } from "./http-BoWBwC0g.js";
|
|
6
6
|
import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-C_cNlNVK.js";
|
|
7
|
-
import {
|
|
8
|
-
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-
|
|
7
|
+
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-1TFakcTF.js";
|
|
8
|
+
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-BdnmnAVc.js";
|
|
9
9
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
10
10
|
import { Router, RouterError, assertPath } from "@fedify/uri-template";
|
|
11
11
|
import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
@@ -281,6 +281,10 @@ var FederationBuilderImpl = class {
|
|
|
281
281
|
callbacks.aliasMapper = mapper;
|
|
282
282
|
return setters;
|
|
283
283
|
},
|
|
284
|
+
mapPortableActorId(mapper) {
|
|
285
|
+
callbacks.portableActorIdMapper = mapper;
|
|
286
|
+
return setters;
|
|
287
|
+
},
|
|
284
288
|
mapActorAlias: (path, identifier) => {
|
|
285
289
|
if (identifier === "") throw new RouterError("Identifier cannot be empty.");
|
|
286
290
|
if (this.router.has(`actorAlias:${identifier}`)) throw new RouterError(`Actor alias for "${identifier}" already set.`);
|
|
@@ -2629,13 +2633,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
|
|
|
2629
2633
|
* @param parameters The parameters for handling the collection.
|
|
2630
2634
|
* @returns A promise that resolves to an HTTP response.
|
|
2631
2635
|
*/
|
|
2632
|
-
async function handleCollection(request, { name: name$
|
|
2633
|
-
const spanName = name$
|
|
2636
|
+
async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
|
|
2637
|
+
const spanName = name$1.trim().replace(/\s+/g, "_");
|
|
2634
2638
|
tracerProvider = tracerProvider ?? trace.getTracerProvider();
|
|
2635
2639
|
const tracer = tracerProvider.getTracer(name, version);
|
|
2636
2640
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
2637
2641
|
const metricBase = {
|
|
2638
|
-
kind: getCollectionMetricKind(name$
|
|
2642
|
+
kind: getCollectionMetricKind(name$1),
|
|
2639
2643
|
page: cursor != null,
|
|
2640
2644
|
dispatcher: "built_in"
|
|
2641
2645
|
};
|
|
@@ -2676,7 +2680,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
2676
2680
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
2677
2681
|
return await onNotFound(request);
|
|
2678
2682
|
}
|
|
2679
|
-
const items = filterCollectionItems(page.items, name$
|
|
2683
|
+
const items = filterCollectionItems(page.items, name$1, filterPredicate);
|
|
2680
2684
|
itemCount = items.length;
|
|
2681
2685
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
2682
2686
|
return items;
|
|
@@ -2716,7 +2720,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
2716
2720
|
} else {
|
|
2717
2721
|
const uri = new URL(baseUri);
|
|
2718
2722
|
uri.searchParams.set("cursor", cursor);
|
|
2719
|
-
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$
|
|
2723
|
+
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
|
|
2720
2724
|
kind: SpanKind.SERVER,
|
|
2721
2725
|
attributes: {
|
|
2722
2726
|
"activitypub.collection.id": uri.href,
|
|
@@ -2732,7 +2736,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
2732
2736
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
2733
2737
|
return await onNotFound(request);
|
|
2734
2738
|
}
|
|
2735
|
-
const items = filterCollectionItems(page.items, name$
|
|
2739
|
+
const items = filterCollectionItems(page.items, name$1, filterPredicate);
|
|
2736
2740
|
itemCount = items.length;
|
|
2737
2741
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
2738
2742
|
return {
|
|
@@ -3424,6 +3428,7 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
3424
3428
|
}
|
|
3425
3429
|
let activity = null;
|
|
3426
3430
|
let activityVerified = false;
|
|
3431
|
+
let proofVerified = false;
|
|
3427
3432
|
if (ldSigVerified) {
|
|
3428
3433
|
logger.debug("Linked Data Signatures are verified.", {
|
|
3429
3434
|
recipient,
|
|
@@ -3440,6 +3445,21 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
3440
3445
|
return await respondInvalidActivity(error);
|
|
3441
3446
|
}
|
|
3442
3447
|
activityVerified = true;
|
|
3448
|
+
if (!skipSignatureVerification && hasPortableActor(activity)) try {
|
|
3449
|
+
proofVerified = await verifyObject(Activity, jsonWithoutSig, {
|
|
3450
|
+
contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
|
|
3451
|
+
documentLoader: ctx.documentLoader,
|
|
3452
|
+
keyCache,
|
|
3453
|
+
meterProvider,
|
|
3454
|
+
tracerProvider
|
|
3455
|
+
}) != null;
|
|
3456
|
+
} catch (error) {
|
|
3457
|
+
if (!isPermanentActivityParseError(error)) throw error;
|
|
3458
|
+
logger.debug("Failed to verify the Object Integrity Proofs of the portable actor's activity:\n{error}", {
|
|
3459
|
+
recipient,
|
|
3460
|
+
error
|
|
3461
|
+
});
|
|
3462
|
+
}
|
|
3443
3463
|
} else {
|
|
3444
3464
|
logger.debug("Linked Data Signatures are not verified.", {
|
|
3445
3465
|
recipient,
|
|
@@ -3496,6 +3516,7 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
3496
3516
|
activity: json
|
|
3497
3517
|
});
|
|
3498
3518
|
activityVerified = true;
|
|
3519
|
+
proofVerified = true;
|
|
3499
3520
|
}
|
|
3500
3521
|
}
|
|
3501
3522
|
let httpSigKey = null;
|
|
@@ -3608,6 +3629,24 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
3608
3629
|
"http_signatures.verified": httpSigKey != null,
|
|
3609
3630
|
"http_signatures.key_id": httpSigKey?.id?.href ?? ""
|
|
3610
3631
|
});
|
|
3632
|
+
if (!skipSignatureVerification && !proofVerified && (hasPortableActor(activity) || isPortableGatewayKey(httpSigKey))) {
|
|
3633
|
+
if (deferredLdSignatureError != null) throw deferredLdSignatureError;
|
|
3634
|
+
logger.error("The activity {activityId} of the portable actor {actorId} is not authenticated by a valid Object Integrity Proof.", {
|
|
3635
|
+
activity: json,
|
|
3636
|
+
recipient,
|
|
3637
|
+
activityId: activity.id?.href,
|
|
3638
|
+
actorId: activity.actorId?.href,
|
|
3639
|
+
keyId: httpSigKey?.id?.href
|
|
3640
|
+
});
|
|
3641
|
+
span.setStatus({
|
|
3642
|
+
code: SpanStatusCode.ERROR,
|
|
3643
|
+
message: `The activity of the portable actor (${activity.actorId?.href}) is not authenticated by a valid Object Integrity Proof.`
|
|
3644
|
+
});
|
|
3645
|
+
return new Response("Activities of portable actors must have valid Object Integrity Proofs.", {
|
|
3646
|
+
status: 401,
|
|
3647
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
3648
|
+
});
|
|
3649
|
+
}
|
|
3611
3650
|
if (httpSigKey != null && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
|
|
3612
3651
|
if (deferredLdSignatureError != null) throw deferredLdSignatureError;
|
|
3613
3652
|
getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure("actorKeyMismatch", httpSigKey.id == null ? void 0 : getRemoteHost(httpSigKey.id));
|
|
@@ -3825,8 +3864,8 @@ var CustomCollectionHandler = class {
|
|
|
3825
3864
|
* @param CollectionPage The CollectionPage constructor.
|
|
3826
3865
|
* @param filterPredicate Optional filter predicate for items.
|
|
3827
3866
|
*/
|
|
3828
|
-
constructor(name$
|
|
3829
|
-
this.name = name$
|
|
3867
|
+
constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
|
|
3868
|
+
this.name = name$2;
|
|
3830
3869
|
this.values = values;
|
|
3831
3870
|
this.context = context;
|
|
3832
3871
|
this.callbacks = callbacks;
|
|
@@ -4310,6 +4349,15 @@ const MIN_COMPONENTS = [
|
|
|
4310
4349
|
"@target-uri",
|
|
4311
4350
|
"@authority"
|
|
4312
4351
|
];
|
|
4352
|
+
/**
|
|
4353
|
+
* Checks whether an HTTP Signature key is a gateway key of an FEP-ef61
|
|
4354
|
+
* portable actor, i.e., its ID is a compatible identifier and it is owned by
|
|
4355
|
+
* a portable actor, which only the portable actor's signed document can
|
|
4356
|
+
* establish.
|
|
4357
|
+
*/
|
|
4358
|
+
function isPortableGatewayKey(key) {
|
|
4359
|
+
return key?.id != null && isCompatibleKeyId(key.id) && key.ownerId != null && isPortableUri(key.ownerId);
|
|
4360
|
+
}
|
|
4313
4361
|
//#endregion
|
|
4314
4362
|
//#region src/nodeinfo/handler.ts
|
|
4315
4363
|
/**
|
|
@@ -4835,37 +4883,48 @@ function formatKeyIds(candidates) {
|
|
|
4835
4883
|
return candidates.map((c) => c.verificationMethod.href).join(", ");
|
|
4836
4884
|
}
|
|
4837
4885
|
/**
|
|
4886
|
+
* Rejects an activity of a portable actor whose ID cannot be authenticated by
|
|
4887
|
+
* the actor's DID: FEP-ef61 requires the activity to be a portable object
|
|
4888
|
+
* signed by that DID, so its ID has to be a portable ID with the same DID.
|
|
4889
|
+
* @throws {TypeError} If the activity is performed by a portable actor, but
|
|
4890
|
+
* its ID is not a portable ID of the actor's DID, or its
|
|
4891
|
+
* portable actors do not share a DID.
|
|
4892
|
+
* @internal
|
|
4893
|
+
*/
|
|
4894
|
+
function assertPortableActorActivity(activity) {
|
|
4895
|
+
const portableActors = activity.actorIds.filter(isPortableUrl);
|
|
4896
|
+
if (portableActors.length < 1) return;
|
|
4897
|
+
const did = getFe34Origin(portableActors[0]);
|
|
4898
|
+
if (portableActors.some((actorId) => getFe34Origin(actorId) !== did)) throw new TypeError(`The activity ${activity.id?.href} has portable actors with different DIDs (${portableActors.map((a) => a.href).join(", ")}); a portable activity is signed by a single DID.`);
|
|
4899
|
+
if (activity.id == null || !isPortableUrl(activity.id) || getFe34Origin(activity.id) !== did) throw new TypeError(`The activity ${activity.id?.href} is performed by the portable actor ${portableActors[0].href}, so its ID has to be an ap: or ap+ef61: URI with the same DID (${did}), as FEP-ef61 requires it to carry an Object Integrity Proof made by that DID.`);
|
|
4900
|
+
}
|
|
4901
|
+
/**
|
|
4838
4902
|
* Creates the Object Integrity Proofs Fedify attaches to an outgoing activity.
|
|
4839
4903
|
*
|
|
4904
|
+
* Keys whose IDs are FEP-ef61 compatible identifiers are gateway keys, which
|
|
4905
|
+
* never sign proofs. A portable activity, i.e., one with an `ap:` or
|
|
4906
|
+
* `ap+ef61:` ID, that carries no proof is signed only by the one Ed25519 key
|
|
4907
|
+
* whose verification method is a DID URL for its DID, even if it is the only
|
|
4908
|
+
* key; if no key or more than one key qualifies, this function throws.
|
|
4909
|
+
*
|
|
4840
4910
|
* Outside the map-local compound-proof profile, i.e., when the activity
|
|
4841
4911
|
* embeds no portable object, every Ed25519 key signs the activity, as it
|
|
4842
4912
|
* always has. Inside the profile, which accepts exactly one direct proof per
|
|
4843
4913
|
* map, Fedify creates at most one proof:
|
|
4844
4914
|
*
|
|
4845
4915
|
* - An activity that already carries a proof is kept as is.
|
|
4846
|
-
* - A single Ed25519 key signs
|
|
4847
|
-
* - With several Ed25519 keys, a portable activity is
|
|
4848
|
-
*
|
|
4849
|
-
* proof the FEP-ef61 policy can accept. If no key or more than one key
|
|
4850
|
-
* qualifies, or the activity is not portable itself, the choice is
|
|
4851
|
-
* ambiguous and this function throws.
|
|
4916
|
+
* - A single Ed25519 key signs a non-portable activity.
|
|
4917
|
+
* - With several Ed25519 keys, a non-portable activity is ambiguous, and
|
|
4918
|
+
* this function throws.
|
|
4852
4919
|
*
|
|
4853
|
-
* @throws {TypeError} If a single proof key cannot be chosen
|
|
4920
|
+
* @throws {TypeError} If a single proof key cannot be chosen, or the activity
|
|
4921
|
+
* is performed by a portable actor but is not a portable
|
|
4922
|
+
* activity of the same DID.
|
|
4854
4923
|
* @internal
|
|
4855
4924
|
*/
|
|
4856
4925
|
async function signOutgoingActivity(activity, candidates, options) {
|
|
4857
4926
|
const { contextLoader, tracerProvider } = options;
|
|
4858
|
-
|
|
4859
|
-
for await (const _ of activity.getProofs({ contextLoader })) {
|
|
4860
|
-
hasProof = true;
|
|
4861
|
-
break;
|
|
4862
|
-
}
|
|
4863
|
-
const keys = candidates.filter((c) => c.privateKey.algorithm.name === "Ed25519");
|
|
4864
|
-
if (keys.length < 1 || hasProof && !options.appendToExistingProofs) return {
|
|
4865
|
-
activity,
|
|
4866
|
-
hasProof,
|
|
4867
|
-
proofCreated: false
|
|
4868
|
-
};
|
|
4927
|
+
assertPortableActorActivity(activity);
|
|
4869
4928
|
const sign = async (selected) => {
|
|
4870
4929
|
for (const { verificationMethod, privateKey } of selected) activity = await signObject(activity, privateKey, verificationMethod, {
|
|
4871
4930
|
contextLoader,
|
|
@@ -4877,6 +4936,18 @@ async function signOutgoingActivity(activity, candidates, options) {
|
|
|
4877
4936
|
proofCreated: true
|
|
4878
4937
|
};
|
|
4879
4938
|
};
|
|
4939
|
+
let hasProof = false;
|
|
4940
|
+
for await (const _ of activity.getProofs({ contextLoader })) {
|
|
4941
|
+
hasProof = true;
|
|
4942
|
+
break;
|
|
4943
|
+
}
|
|
4944
|
+
const keys = candidates.filter((c) => c.privateKey.algorithm.name === "Ed25519" && !isCompatibleKeyId(c.verificationMethod));
|
|
4945
|
+
if (!hasProof && activity.id != null && isPortableUrl(activity.id)) return await sign(selectPortableActivityKey(activity, activity.id, keys));
|
|
4946
|
+
if (keys.length < 1 || hasProof && !options.appendToExistingProofs) return {
|
|
4947
|
+
activity,
|
|
4948
|
+
hasProof,
|
|
4949
|
+
proofCreated: false
|
|
4950
|
+
};
|
|
4880
4951
|
if (keys.length < 2 && !hasProof) return await sign(keys);
|
|
4881
4952
|
if (!containsCompoundPortableObject(await activity.toJsonLd({
|
|
4882
4953
|
format: "compact",
|
|
@@ -4896,15 +4967,26 @@ async function signOutgoingActivity(activity, candidates, options) {
|
|
|
4896
4967
|
proofCreated: false
|
|
4897
4968
|
};
|
|
4898
4969
|
}
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4970
|
+
throw new TypeError(`Cannot choose an Object Integrity Proof key for the activity ${activityId}: it embeds portable objects, so it falls under the map-local compound-proof profile, which accepts exactly one direct proof per map, but ${keys.length} Ed25519 keys were supplied (${formatKeyIds(keys)}). Send it with explicit sender keys that contain exactly one Ed25519 key (RSA keys for HTTP Signatures may stay), or sign it with signObject() before sending it.`);
|
|
4971
|
+
}
|
|
4972
|
+
/**
|
|
4973
|
+
* Chooses the one Ed25519 key that can sign a portable activity: the key
|
|
4974
|
+
* whose verification method is a DID URL for the activity's DID.
|
|
4975
|
+
* @throws {TypeError} If no key or more than one key qualifies.
|
|
4976
|
+
*/
|
|
4977
|
+
function selectPortableActivityKey(activity, activityId, keys) {
|
|
4978
|
+
const activityDid = getFe34Origin(activityId);
|
|
4979
|
+
const eligible = keys.filter((c) => c.verificationMethod.protocol === "did:" && haveSameFe34Origin(activityId, c.verificationMethod));
|
|
4980
|
+
if (eligible.length !== 1) throw new TypeError(`Cannot choose an Object Integrity Proof key for the portable activity ${activityId.href}: ` + (eligible.length < 1 ? `none of its ${keys.length} Ed25519 keys (${formatKeyIds(keys)}) ` : `${eligible.length} of its Ed25519 keys (${formatKeyIds(eligible)}) `) + `${eligible.length < 1 ? "is" : "are"} a DID URL verification method for ${activityDid}. FEP-ef61 requires a portable activity's proof to be made by its DID, and the map-local compound-proof profile accepts exactly one direct proof per map. Gateway keys, whose IDs are compatible identifiers, never sign proofs. Supply exactly one Ed25519 key whose key ID is a DID URL for ${activityDid}, or sign the activity with signObject() before sending it.`);
|
|
4981
|
+
getLogger([
|
|
4982
|
+
"fedify",
|
|
4983
|
+
"federation",
|
|
4984
|
+
"outbox"
|
|
4985
|
+
]).debug("The portable activity {activityId} is signed only by {keyId}, the one Ed25519 key whose verification method matches its DID.", {
|
|
4986
|
+
activityId: activity.id?.href,
|
|
4905
4987
|
keyId: eligible[0].verificationMethod.href
|
|
4906
4988
|
});
|
|
4907
|
-
return
|
|
4989
|
+
return eligible;
|
|
4908
4990
|
}
|
|
4909
4991
|
/**
|
|
4910
4992
|
* Rejects an outgoing activity whose proofs Fedify's own inbox would reject
|
|
@@ -6788,9 +6870,10 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6788
6870
|
const contextLoader = this.contextLoaderFactory(this.#getLoaderOptions(ctx.origin));
|
|
6789
6871
|
const activityId = activity.id.href;
|
|
6790
6872
|
let rsaKey = null;
|
|
6873
|
+
const ldSignable = !hasPortableActor(activity);
|
|
6791
6874
|
for (const { keyId, privateKey } of keys) {
|
|
6792
6875
|
validateCryptoKey(privateKey, "private");
|
|
6793
|
-
if (rsaKey == null && privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") rsaKey = {
|
|
6876
|
+
if (ldSignable && rsaKey == null && privateKey.algorithm.name === "RSASSA-PKCS1-v1_5" && !isCompatibleKeyId(keyId)) rsaKey = {
|
|
6794
6877
|
keyId,
|
|
6795
6878
|
privateKey
|
|
6796
6879
|
};
|
|
@@ -6811,7 +6894,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6811
6894
|
});
|
|
6812
6895
|
if (proofCreated || !hasProof || options.normalizeExistingProofs) jsonLd = await normalizeOutgoingActivityJsonLd(jsonLd, contextLoader, { preserveNestedSecuredDocuments: true });
|
|
6813
6896
|
assertSupportedCompoundProofShape(jsonLd, activityId);
|
|
6814
|
-
if (rsaKey == null) logger.
|
|
6897
|
+
if (rsaKey == null && !ldSignable) logger.debug("The activity {activityId} is performed by a portable actor, so it is sent without a Linked Data signature.", { activityId });
|
|
6898
|
+
else if (rsaKey == null) logger.warn("No supported key found to create a Linked Data signature for the activity {activityId}. The activity will be sent without a Linked Data signature. In order to create a Linked Data signature, at least one RSASSA-PKCS1-v1_5 key must be provided.", {
|
|
6815
6899
|
activityId,
|
|
6816
6900
|
keys: keys.map((pair) => ({
|
|
6817
6901
|
keyId: pair.keyId.href,
|
|
@@ -7687,14 +7771,15 @@ var ContextImpl = class ContextImpl {
|
|
|
7687
7771
|
getActorKeyPairsIdentifier: identifier,
|
|
7688
7772
|
actorKeyPairsDispatcherIdentifier: this.invokedFromActorKeyPairsDispatcher.identifier
|
|
7689
7773
|
});
|
|
7774
|
+
const portableActor = await this.getPortableActorKeyOwner(identifier);
|
|
7690
7775
|
let keyPairs;
|
|
7691
7776
|
try {
|
|
7692
|
-
keyPairs = await this.getKeyPairsFromIdentifier(identifier);
|
|
7777
|
+
keyPairs = await this.getKeyPairsFromIdentifier(identifier, portableActor);
|
|
7693
7778
|
} catch (_) {
|
|
7694
7779
|
logger.warn("No actor key pairs dispatcher registered.");
|
|
7695
7780
|
return [];
|
|
7696
7781
|
}
|
|
7697
|
-
const owner = this.getActorUri(identifier);
|
|
7782
|
+
const owner = portableActor?.id ?? this.getActorUri(identifier);
|
|
7698
7783
|
const result = [];
|
|
7699
7784
|
let i = 1;
|
|
7700
7785
|
for (const keyPair of keyPairs) {
|
|
@@ -7706,7 +7791,7 @@ var ContextImpl = class ContextImpl {
|
|
|
7706
7791
|
publicKey: keyPair.publicKey
|
|
7707
7792
|
}),
|
|
7708
7793
|
multikey: new Multikey({
|
|
7709
|
-
id: new URL(`#multikey-${i}`, owner),
|
|
7794
|
+
id: portableActor == null ? new URL(`#multikey-${i}`, owner) : keyPair.keyId,
|
|
7710
7795
|
controller: owner,
|
|
7711
7796
|
publicKey: keyPair.publicKey
|
|
7712
7797
|
})
|
|
@@ -7716,7 +7801,26 @@ var ContextImpl = class ContextImpl {
|
|
|
7716
7801
|
}
|
|
7717
7802
|
return result;
|
|
7718
7803
|
}
|
|
7719
|
-
|
|
7804
|
+
/**
|
|
7805
|
+
* Resolves the portable actor that the actor with the given identifier is,
|
|
7806
|
+
* through the portable actor ID mapper, along with the base of its gateway
|
|
7807
|
+
* key IDs on this server.
|
|
7808
|
+
* @param identifier The actor's identifier.
|
|
7809
|
+
* @returns The portable actor's ID and key ID base, or `null` if the actor
|
|
7810
|
+
* is not portable.
|
|
7811
|
+
* @throws {TypeError} If the mapper returns an invalid portable actor ID.
|
|
7812
|
+
*/
|
|
7813
|
+
async getPortableActorKeyOwner(identifier) {
|
|
7814
|
+
const mapper = this.federation.actorCallbacks?.portableActorIdMapper;
|
|
7815
|
+
if (mapper == null) return null;
|
|
7816
|
+
const id = await mapper(this, identifier);
|
|
7817
|
+
if (id == null) return null;
|
|
7818
|
+
return {
|
|
7819
|
+
id,
|
|
7820
|
+
keyBase: getGatewayKeyBase(id, this.canonicalOrigin)
|
|
7821
|
+
};
|
|
7822
|
+
}
|
|
7823
|
+
async getKeyPairsFromIdentifier(identifier, portableActor) {
|
|
7720
7824
|
const logger = getLogger([
|
|
7721
7825
|
"fedify",
|
|
7722
7826
|
"federation",
|
|
@@ -7733,6 +7837,8 @@ var ContextImpl = class ContextImpl {
|
|
|
7733
7837
|
}
|
|
7734
7838
|
throw error;
|
|
7735
7839
|
}
|
|
7840
|
+
if (portableActor === void 0) portableActor = await this.getPortableActorKeyOwner(identifier);
|
|
7841
|
+
const keyBase = portableActor?.keyBase ?? actorUri;
|
|
7736
7842
|
const keyPairs = await this.federation.actorCallbacks?.keyPairsDispatcher(new ContextImpl({
|
|
7737
7843
|
...this,
|
|
7738
7844
|
invokedFromActorKeyPairsDispatcher: { identifier }
|
|
@@ -7743,7 +7849,7 @@ var ContextImpl = class ContextImpl {
|
|
|
7743
7849
|
for (const keyPair of keyPairs) {
|
|
7744
7850
|
result.push({
|
|
7745
7851
|
...keyPair,
|
|
7746
|
-
keyId: new URL(i == 0 ? `#main-key` : `#key-${i + 1}`,
|
|
7852
|
+
keyId: new URL(i == 0 ? `#main-key` : `#key-${i + 1}`, keyBase)
|
|
7747
7853
|
});
|
|
7748
7854
|
i++;
|
|
7749
7855
|
}
|