@fedify/fedify 2.4.0-dev.1962 → 2.4.0-dev.1988
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-CWYQXC2W.mjs → builder-Cts78G9N.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/{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-CfLMX4HF.mjs → deno-BgMEefEr.mjs} +1 -1
- package/dist/{docloader-CDmmYxYb.mjs → docloader-DEYs99FZ.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- 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-CIeDMHxZ.cjs → federation-DzPUG_AP.cjs} +93 -17
- package/dist/{http-6TI0tI03.d.cts → http-65PYi9jY.d.ts} +34 -2
- package/dist/{http-CdrSn2ip.d.ts → http-CP-7OdSq.d.cts} +34 -2
- package/dist/{http-DWZZicb2.js → http-CgwqtxAy.js} +169 -7
- package/dist/{http-BR190IDB.mjs → http-CtidZS_t.mjs} +3 -3
- package/dist/{http-C3kkrvAE.cjs → http-Z3anvCd-.cjs} +180 -6
- package/dist/{key-BcA-AzPp.mjs → key-Cwbg5ggV.mjs} +169 -7
- package/dist/{keycache-ZLqIfrkL.mjs → keycache-BUDHB5rY.mjs} +12 -4
- package/dist/{kv-cache-DCItgZUM.cjs → kv-cache-BM2-w-Wq.cjs} +2 -2
- package/dist/{kv-cache-5d2HgB_-.js → kv-cache-CykuL0ya.js} +2 -2
- package/dist/{kv-cache-m962_cok.mjs → kv-cache-DHiR8yTf.mjs} +1 -1
- package/dist/{ld-QpPkmUjy.mjs → ld-CzhdWyTt.mjs} +3 -3
- package/dist/{metrics-Dkyzq7ml.mjs → metrics-CrqsHP0l.mjs} +1 -1
- package/dist/{middleware-BdKtWJK1.mjs → middleware-CZpYxSKD.mjs} +1 -1
- package/dist/{middleware-BNktRUnj.js → middleware-CeCj5caM.js} +101 -25
- package/dist/{middleware-BaZ32nCi.mjs → middleware-DCpiAsZi.mjs} +34 -23
- 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 +5 -5
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +28 -1
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/{owner-r_waTiQj.d.cts → owner-BF0L9cMt.d.cts} +11 -0
- package/dist/{owner-DGSfntI-.d.ts → owner-BSzr5-I8.d.ts} +11 -0
- package/dist/owner-BqVXJLW8.mjs +175 -0
- package/dist/{proof-DrWB_vel.js → proof-CUghqhAf.js} +122 -65
- package/dist/{proof-CYdLcTFg.cjs → proof-DHu5zI0Z.cjs} +122 -65
- package/dist/{proof-Mov6-gSw.mjs → proof-DL8Iid3a.mjs} +14 -11
- package/dist/{send-DB1mkFKO.mjs → send-6jS4xFsX.mjs} +68 -11
- 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 +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 +103 -4
- package/dist/sig/proof.test.mjs +143 -2
- package/dist/{temporal-Yi0JAV9z.mjs → temporal-DcyzeytP.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 +7 -7
- package/dist/owner-B795xXwU.mjs +0 -120
|
@@ -3,13 +3,13 @@ let _logtape_logtape = require("@logtape/logtape");
|
|
|
3
3
|
let _fedify_vocab = require("@fedify/vocab");
|
|
4
4
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
5
5
|
let byte_encodings_hex = require("byte-encodings/hex");
|
|
6
|
-
let structured_field_values = require("structured-field-values");
|
|
7
6
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
7
|
+
let structured_field_values = require("structured-field-values");
|
|
8
8
|
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
9
9
|
let byte_encodings_base64 = require("byte-encodings/base64");
|
|
10
10
|
//#region deno.json
|
|
11
11
|
var name = "@fedify/fedify";
|
|
12
|
-
var version = "2.4.0-dev.
|
|
12
|
+
var version = "2.4.0-dev.1988+c5c9d617";
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region ../../node_modules/.pnpm/temporal-polyfill@1.0.1/node_modules/temporal-polyfill/chunks/root.js
|
|
15
15
|
const NativeTemporal = globalThis.Temporal;
|
|
@@ -4603,6 +4603,118 @@ async function importJwk(jwk, type) {
|
|
|
4603
4603
|
validateCryptoKey(key, type);
|
|
4604
4604
|
return key;
|
|
4605
4605
|
}
|
|
4606
|
+
/**
|
|
4607
|
+
* Fetches the document that the given actor URI dereferences to, and returns
|
|
4608
|
+
* the actor only if the host that served the document is authoritative for
|
|
4609
|
+
* the id the document claims.
|
|
4610
|
+
*
|
|
4611
|
+
* Only the origin that serves an actor id can speak for it. Without that
|
|
4612
|
+
* rule any host could serve a document describing somebody else's actor—and
|
|
4613
|
+
* listing its own keys as that actor's.
|
|
4614
|
+
*
|
|
4615
|
+
* @param actorId The URI of the actor to fetch.
|
|
4616
|
+
* @param options Options for fetching the document.
|
|
4617
|
+
* @returns The actor, or `null` if the document cannot be fetched, is not an
|
|
4618
|
+
* actor, or belongs to another origin.
|
|
4619
|
+
* @internal
|
|
4620
|
+
*/
|
|
4621
|
+
async function fetchActorDocument(actorId, options = {}) {
|
|
4622
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
4623
|
+
"fedify",
|
|
4624
|
+
"sig",
|
|
4625
|
+
"key"
|
|
4626
|
+
]);
|
|
4627
|
+
const documentLoader = options.documentLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)();
|
|
4628
|
+
const contextLoader = options.contextLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)();
|
|
4629
|
+
const { tracerProvider } = options;
|
|
4630
|
+
let document;
|
|
4631
|
+
let documentUrl = actorId;
|
|
4632
|
+
try {
|
|
4633
|
+
const remoteDocument = await documentLoader(actorId.href);
|
|
4634
|
+
document = remoteDocument.document;
|
|
4635
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", actorId);
|
|
4636
|
+
} catch (error) {
|
|
4637
|
+
logger.debug("Failed to fetch the actor {actorId}: {error}", {
|
|
4638
|
+
actorId: actorId.href,
|
|
4639
|
+
error
|
|
4640
|
+
});
|
|
4641
|
+
return null;
|
|
4642
|
+
}
|
|
4643
|
+
let object;
|
|
4644
|
+
try {
|
|
4645
|
+
object = await _fedify_vocab.Object.fromJsonLd(document, {
|
|
4646
|
+
documentLoader,
|
|
4647
|
+
contextLoader,
|
|
4648
|
+
tracerProvider,
|
|
4649
|
+
baseUrl: documentUrl
|
|
4650
|
+
});
|
|
4651
|
+
} catch (error) {
|
|
4652
|
+
if (!(error instanceof TypeError)) throw error;
|
|
4653
|
+
logger.debug("The document served at {documentUrl} is not a valid object: {error}", {
|
|
4654
|
+
documentUrl: documentUrl.href,
|
|
4655
|
+
error
|
|
4656
|
+
});
|
|
4657
|
+
return null;
|
|
4658
|
+
}
|
|
4659
|
+
if (!(0, _fedify_vocab.isActor)(object)) return null;
|
|
4660
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
4661
|
+
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.", {
|
|
4662
|
+
documentUrl: documentUrl.href,
|
|
4663
|
+
actorId: object.id?.href
|
|
4664
|
+
});
|
|
4665
|
+
return null;
|
|
4666
|
+
}
|
|
4667
|
+
return object;
|
|
4668
|
+
}
|
|
4669
|
+
/**
|
|
4670
|
+
* Resolves the owner that the given key claims, and returns it only if the
|
|
4671
|
+
* claim holds.
|
|
4672
|
+
*
|
|
4673
|
+
* The `owner` of a {@link CryptographicKey}, like the `controller` of
|
|
4674
|
+
* a {@link Multikey}, proves nothing on its own: the key document and the
|
|
4675
|
+
* claim inside it are served by the same host, so anyone able to serve a key
|
|
4676
|
+
* document can name any actor in the world as its owner. The claim becomes
|
|
4677
|
+
* meaningful only once the named actor's own document is fetched and turns
|
|
4678
|
+
* out to link back to the key. This function performs that mutual-link
|
|
4679
|
+
* check, which is the only sound way to learn a fetched key's owner.
|
|
4680
|
+
*
|
|
4681
|
+
* The claimed owner is always dereferenced, never read out of the key
|
|
4682
|
+
* document: an owner embedded there is written by whoever wrote the key.
|
|
4683
|
+
*
|
|
4684
|
+
* @param key The key whose ownership claim is to be verified. It must carry
|
|
4685
|
+
* an `id`, as that is what the owner has to link back to.
|
|
4686
|
+
* @param options Options for fetching the claimed owner.
|
|
4687
|
+
* @returns The verified owner, or `null` if the key claims no owner, the
|
|
4688
|
+
* owner cannot be fetched, or the owner does not link back to
|
|
4689
|
+
* the key.
|
|
4690
|
+
* @internal
|
|
4691
|
+
*/
|
|
4692
|
+
async function verifyKeyOwnership(key, options = {}) {
|
|
4693
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
4694
|
+
"fedify",
|
|
4695
|
+
"sig",
|
|
4696
|
+
"key"
|
|
4697
|
+
]);
|
|
4698
|
+
const keyId = key.id;
|
|
4699
|
+
if (keyId == null) return null;
|
|
4700
|
+
const claimedOwnerId = key instanceof _fedify_vocab.CryptographicKey ? key.ownerId : key.controllerId;
|
|
4701
|
+
if (claimedOwnerId == null) return null;
|
|
4702
|
+
const owner = await fetchActorDocument(claimedOwnerId, options);
|
|
4703
|
+
if (owner == null) {
|
|
4704
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
|
|
4705
|
+
keyId: keyId.href,
|
|
4706
|
+
claimedOwnerId: claimedOwnerId.href
|
|
4707
|
+
});
|
|
4708
|
+
return null;
|
|
4709
|
+
}
|
|
4710
|
+
const linkedKeyIds = key instanceof _fedify_vocab.CryptographicKey ? owner.publicKeyIds : owner.assertionMethodIds;
|
|
4711
|
+
for (const linkedKeyId of linkedKeyIds) if (linkedKeyId.href === keyId.href) return owner;
|
|
4712
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims does not list the key as its own.", {
|
|
4713
|
+
keyId: keyId.href,
|
|
4714
|
+
claimedOwnerId: claimedOwnerId.href
|
|
4715
|
+
});
|
|
4716
|
+
return null;
|
|
4717
|
+
}
|
|
4606
4718
|
async function withFetchKeySpan(keyId, tracerProvider, fetcher) {
|
|
4607
4719
|
tracerProvider ??= _opentelemetry_api.trace.getTracerProvider();
|
|
4608
4720
|
return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.fetch_key", {
|
|
@@ -4736,6 +4848,18 @@ function checkCachedKeyHit(cachedKey, keyId, cls, logger) {
|
|
|
4736
4848
|
async function clearFetchErrorMetadata(keyId, keyCache) {
|
|
4737
4849
|
await keyCache?.setFetchError?.(keyId, null);
|
|
4738
4850
|
}
|
|
4851
|
+
/**
|
|
4852
|
+
* Returns the identifier a fragment-bearing DID URL is a fragment of, or
|
|
4853
|
+
* `null` if the URL is not a DID URL. `did:web:example.com#key` is thereby
|
|
4854
|
+
* traced back to `did:web:example.com`, the DID that resolving the key went
|
|
4855
|
+
* through in the first place.
|
|
4856
|
+
*/
|
|
4857
|
+
function stripFragment(keyId) {
|
|
4858
|
+
if (keyId.protocol !== "did:") return null;
|
|
4859
|
+
const did = new URL(keyId.href);
|
|
4860
|
+
did.hash = "";
|
|
4861
|
+
return did;
|
|
4862
|
+
}
|
|
4739
4863
|
function isDidKeyUrl(keyId) {
|
|
4740
4864
|
return keyId.protocol === "did:" && keyId.pathname.startsWith("key:");
|
|
4741
4865
|
}
|
|
@@ -4791,7 +4915,7 @@ async function resolveDidKey(cacheKey, cls, keyCache, logger) {
|
|
|
4791
4915
|
cached: false
|
|
4792
4916
|
};
|
|
4793
4917
|
}
|
|
4794
|
-
async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
|
|
4918
|
+
async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider, keyIdBoundByCaller }, logger) {
|
|
4795
4919
|
let object;
|
|
4796
4920
|
try {
|
|
4797
4921
|
object = await _fedify_vocab.Object.fromJsonLd(document, {
|
|
@@ -4821,8 +4945,22 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
4821
4945
|
}
|
|
4822
4946
|
}
|
|
4823
4947
|
let key = null;
|
|
4824
|
-
|
|
4948
|
+
let ownerDocument = null;
|
|
4949
|
+
if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object.id == null ? object.clone({ id: cacheKey }) : object;
|
|
4825
4950
|
else if ((0, _fedify_vocab.isActor)(object)) {
|
|
4951
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
4952
|
+
logger.debug("Failed to verify; the document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin.", {
|
|
4953
|
+
keyId,
|
|
4954
|
+
documentUrl: documentUrl.href,
|
|
4955
|
+
actorId: object.id?.href
|
|
4956
|
+
});
|
|
4957
|
+
await keyCache?.set(cacheKey, null);
|
|
4958
|
+
return {
|
|
4959
|
+
key: null,
|
|
4960
|
+
cached: false
|
|
4961
|
+
};
|
|
4962
|
+
}
|
|
4963
|
+
ownerDocument = object;
|
|
4826
4964
|
const keys = cls === _fedify_vocab.CryptographicKey ? object.getPublicKeys({
|
|
4827
4965
|
documentLoader,
|
|
4828
4966
|
contextLoader,
|
|
@@ -4884,6 +5022,27 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
4884
5022
|
cached: false
|
|
4885
5023
|
};
|
|
4886
5024
|
}
|
|
5025
|
+
const claimedOwnerId = key instanceof _fedify_vocab.CryptographicKey ? key.ownerId : key.controllerId;
|
|
5026
|
+
if (!(keyIdBoundByCaller && claimedOwnerId != null && claimedOwnerId.href === stripFragment(cacheKey)?.href)) {
|
|
5027
|
+
if (ownerDocument != null && claimedOwnerId == null) key = key instanceof _fedify_vocab.CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
|
|
5028
|
+
else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
|
|
5029
|
+
if (await verifyKeyOwnership(key, {
|
|
5030
|
+
documentLoader,
|
|
5031
|
+
contextLoader,
|
|
5032
|
+
tracerProvider
|
|
5033
|
+
}) == null) {
|
|
5034
|
+
logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
|
|
5035
|
+
keyId,
|
|
5036
|
+
claimedOwnerId: claimedOwnerId.href
|
|
5037
|
+
});
|
|
5038
|
+
await keyCache?.set(cacheKey, null);
|
|
5039
|
+
return {
|
|
5040
|
+
key: null,
|
|
5041
|
+
cached: false
|
|
5042
|
+
};
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
5045
|
+
}
|
|
4887
5046
|
if (keyCache != null) {
|
|
4888
5047
|
await keyCache.set(cacheKey, key);
|
|
4889
5048
|
logger.debug("Key {keyId} cached.", { keyId });
|
|
@@ -4922,15 +5081,18 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
|
|
|
4922
5081
|
}
|
|
4923
5082
|
logger.debug("Fetching key {keyId} to verify signature...", { keyId });
|
|
4924
5083
|
let document;
|
|
5084
|
+
let documentUrl = cacheKey;
|
|
4925
5085
|
try {
|
|
4926
|
-
|
|
5086
|
+
const remoteDocument = await (options.documentLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)())(keyId);
|
|
5087
|
+
document = remoteDocument.document;
|
|
5088
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", cacheKey);
|
|
4927
5089
|
} catch (error) {
|
|
4928
5090
|
const classified = classifyFetchError(error);
|
|
4929
5091
|
const errored = await onFetchError(error, cacheKey, keyId, keyCache, logger);
|
|
4930
5092
|
outcome = classified;
|
|
4931
5093
|
return errored;
|
|
4932
5094
|
}
|
|
4933
|
-
const resolved = await resolveFetchedKey(document, cacheKey, keyId, cls, options, logger);
|
|
5095
|
+
const resolved = await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
|
|
4934
5096
|
outcome = { result: resolved.key != null ? "fetched" : "invalid" };
|
|
4935
5097
|
return resolved;
|
|
4936
5098
|
} finally {
|
|
@@ -6064,6 +6226,12 @@ Object.defineProperty(exports, "exportJwk", {
|
|
|
6064
6226
|
return exportJwk;
|
|
6065
6227
|
}
|
|
6066
6228
|
});
|
|
6229
|
+
Object.defineProperty(exports, "fetchActorDocument", {
|
|
6230
|
+
enumerable: true,
|
|
6231
|
+
get: function() {
|
|
6232
|
+
return fetchActorDocument;
|
|
6233
|
+
}
|
|
6234
|
+
});
|
|
6067
6235
|
Object.defineProperty(exports, "fetchKey", {
|
|
6068
6236
|
enumerable: true,
|
|
6069
6237
|
get: function() {
|
|
@@ -6244,6 +6412,12 @@ Object.defineProperty(exports, "validateCryptoKey", {
|
|
|
6244
6412
|
return validateCryptoKey;
|
|
6245
6413
|
}
|
|
6246
6414
|
});
|
|
6415
|
+
Object.defineProperty(exports, "verifyKeyOwnership", {
|
|
6416
|
+
enumerable: true,
|
|
6417
|
+
get: function() {
|
|
6418
|
+
return verifyKeyOwnership;
|
|
6419
|
+
}
|
|
6420
|
+
});
|
|
6247
6421
|
Object.defineProperty(exports, "verifyRequest", {
|
|
6248
6422
|
enumerable: true,
|
|
6249
6423
|
get: function() {
|
|
@@ -1,8 +1,8 @@
|
|
|
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 { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-
|
|
4
|
+
import { n as version, t as name } from "./deno-BgMEefEr.mjs";
|
|
5
|
+
import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-CrqsHP0l.mjs";
|
|
6
6
|
import { getLogger } from "@logtape/logtape";
|
|
7
7
|
import { CryptographicKey, Multikey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
8
8
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -86,6 +86,118 @@ async function importJwk(jwk, type) {
|
|
|
86
86
|
validateCryptoKey(key, type);
|
|
87
87
|
return key;
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Fetches the document that the given actor URI dereferences to, and returns
|
|
91
|
+
* the actor only if the host that served the document is authoritative for
|
|
92
|
+
* the id the document claims.
|
|
93
|
+
*
|
|
94
|
+
* Only the origin that serves an actor id can speak for it. Without that
|
|
95
|
+
* rule any host could serve a document describing somebody else's actor—and
|
|
96
|
+
* listing its own keys as that actor's.
|
|
97
|
+
*
|
|
98
|
+
* @param actorId The URI of the actor to fetch.
|
|
99
|
+
* @param options Options for fetching the document.
|
|
100
|
+
* @returns The actor, or `null` if the document cannot be fetched, is not an
|
|
101
|
+
* actor, or belongs to another origin.
|
|
102
|
+
* @internal
|
|
103
|
+
*/
|
|
104
|
+
async function fetchActorDocument(actorId, options = {}) {
|
|
105
|
+
const logger = getLogger([
|
|
106
|
+
"fedify",
|
|
107
|
+
"sig",
|
|
108
|
+
"key"
|
|
109
|
+
]);
|
|
110
|
+
const documentLoader = options.documentLoader ?? getDocumentLoader();
|
|
111
|
+
const contextLoader = options.contextLoader ?? getDocumentLoader();
|
|
112
|
+
const { tracerProvider } = options;
|
|
113
|
+
let document;
|
|
114
|
+
let documentUrl = actorId;
|
|
115
|
+
try {
|
|
116
|
+
const remoteDocument = await documentLoader(actorId.href);
|
|
117
|
+
document = remoteDocument.document;
|
|
118
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", actorId);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
logger.debug("Failed to fetch the actor {actorId}: {error}", {
|
|
121
|
+
actorId: actorId.href,
|
|
122
|
+
error
|
|
123
|
+
});
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
let object;
|
|
127
|
+
try {
|
|
128
|
+
object = await Object$1.fromJsonLd(document, {
|
|
129
|
+
documentLoader,
|
|
130
|
+
contextLoader,
|
|
131
|
+
tracerProvider,
|
|
132
|
+
baseUrl: documentUrl
|
|
133
|
+
});
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (!(error instanceof TypeError)) throw error;
|
|
136
|
+
logger.debug("The document served at {documentUrl} is not a valid object: {error}", {
|
|
137
|
+
documentUrl: documentUrl.href,
|
|
138
|
+
error
|
|
139
|
+
});
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
if (!isActor(object)) return null;
|
|
143
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
144
|
+
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.", {
|
|
145
|
+
documentUrl: documentUrl.href,
|
|
146
|
+
actorId: object.id?.href
|
|
147
|
+
});
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return object;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Resolves the owner that the given key claims, and returns it only if the
|
|
154
|
+
* claim holds.
|
|
155
|
+
*
|
|
156
|
+
* The `owner` of a {@link CryptographicKey}, like the `controller` of
|
|
157
|
+
* a {@link Multikey}, proves nothing on its own: the key document and the
|
|
158
|
+
* claim inside it are served by the same host, so anyone able to serve a key
|
|
159
|
+
* document can name any actor in the world as its owner. The claim becomes
|
|
160
|
+
* meaningful only once the named actor's own document is fetched and turns
|
|
161
|
+
* out to link back to the key. This function performs that mutual-link
|
|
162
|
+
* check, which is the only sound way to learn a fetched key's owner.
|
|
163
|
+
*
|
|
164
|
+
* The claimed owner is always dereferenced, never read out of the key
|
|
165
|
+
* document: an owner embedded there is written by whoever wrote the key.
|
|
166
|
+
*
|
|
167
|
+
* @param key The key whose ownership claim is to be verified. It must carry
|
|
168
|
+
* an `id`, as that is what the owner has to link back to.
|
|
169
|
+
* @param options Options for fetching the claimed owner.
|
|
170
|
+
* @returns The verified owner, or `null` if the key claims no owner, the
|
|
171
|
+
* owner cannot be fetched, or the owner does not link back to
|
|
172
|
+
* the key.
|
|
173
|
+
* @internal
|
|
174
|
+
*/
|
|
175
|
+
async function verifyKeyOwnership(key, options = {}) {
|
|
176
|
+
const logger = getLogger([
|
|
177
|
+
"fedify",
|
|
178
|
+
"sig",
|
|
179
|
+
"key"
|
|
180
|
+
]);
|
|
181
|
+
const keyId = key.id;
|
|
182
|
+
if (keyId == null) return null;
|
|
183
|
+
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
184
|
+
if (claimedOwnerId == null) return null;
|
|
185
|
+
const owner = await fetchActorDocument(claimedOwnerId, options);
|
|
186
|
+
if (owner == null) {
|
|
187
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
|
|
188
|
+
keyId: keyId.href,
|
|
189
|
+
claimedOwnerId: claimedOwnerId.href
|
|
190
|
+
});
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const linkedKeyIds = key instanceof CryptographicKey ? owner.publicKeyIds : owner.assertionMethodIds;
|
|
194
|
+
for (const linkedKeyId of linkedKeyIds) if (linkedKeyId.href === keyId.href) return owner;
|
|
195
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims does not list the key as its own.", {
|
|
196
|
+
keyId: keyId.href,
|
|
197
|
+
claimedOwnerId: claimedOwnerId.href
|
|
198
|
+
});
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
89
201
|
async function withFetchKeySpan(keyId, tracerProvider, fetcher) {
|
|
90
202
|
tracerProvider ??= trace.getTracerProvider();
|
|
91
203
|
return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.fetch_key", {
|
|
@@ -219,6 +331,18 @@ function checkCachedKeyHit(cachedKey, keyId, cls, logger) {
|
|
|
219
331
|
async function clearFetchErrorMetadata(keyId, keyCache) {
|
|
220
332
|
await keyCache?.setFetchError?.(keyId, null);
|
|
221
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Returns the identifier a fragment-bearing DID URL is a fragment of, or
|
|
336
|
+
* `null` if the URL is not a DID URL. `did:web:example.com#key` is thereby
|
|
337
|
+
* traced back to `did:web:example.com`, the DID that resolving the key went
|
|
338
|
+
* through in the first place.
|
|
339
|
+
*/
|
|
340
|
+
function stripFragment(keyId) {
|
|
341
|
+
if (keyId.protocol !== "did:") return null;
|
|
342
|
+
const did = new URL(keyId.href);
|
|
343
|
+
did.hash = "";
|
|
344
|
+
return did;
|
|
345
|
+
}
|
|
222
346
|
function isDidKeyUrl(keyId) {
|
|
223
347
|
return keyId.protocol === "did:" && keyId.pathname.startsWith("key:");
|
|
224
348
|
}
|
|
@@ -274,7 +398,7 @@ async function resolveDidKey(cacheKey, cls, keyCache, logger) {
|
|
|
274
398
|
cached: false
|
|
275
399
|
};
|
|
276
400
|
}
|
|
277
|
-
async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
|
|
401
|
+
async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider, keyIdBoundByCaller }, logger) {
|
|
278
402
|
let object;
|
|
279
403
|
try {
|
|
280
404
|
object = await Object$1.fromJsonLd(document, {
|
|
@@ -304,8 +428,22 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
304
428
|
}
|
|
305
429
|
}
|
|
306
430
|
let key = null;
|
|
307
|
-
|
|
431
|
+
let ownerDocument = null;
|
|
432
|
+
if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object.id == null ? object.clone({ id: cacheKey }) : object;
|
|
308
433
|
else if (isActor(object)) {
|
|
434
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
435
|
+
logger.debug("Failed to verify; the document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin.", {
|
|
436
|
+
keyId,
|
|
437
|
+
documentUrl: documentUrl.href,
|
|
438
|
+
actorId: object.id?.href
|
|
439
|
+
});
|
|
440
|
+
await keyCache?.set(cacheKey, null);
|
|
441
|
+
return {
|
|
442
|
+
key: null,
|
|
443
|
+
cached: false
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
ownerDocument = object;
|
|
309
447
|
const keys = cls === CryptographicKey ? object.getPublicKeys({
|
|
310
448
|
documentLoader,
|
|
311
449
|
contextLoader,
|
|
@@ -367,6 +505,27 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
367
505
|
cached: false
|
|
368
506
|
};
|
|
369
507
|
}
|
|
508
|
+
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
509
|
+
if (!(keyIdBoundByCaller && claimedOwnerId != null && claimedOwnerId.href === stripFragment(cacheKey)?.href)) {
|
|
510
|
+
if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
|
|
511
|
+
else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
|
|
512
|
+
if (await verifyKeyOwnership(key, {
|
|
513
|
+
documentLoader,
|
|
514
|
+
contextLoader,
|
|
515
|
+
tracerProvider
|
|
516
|
+
}) == null) {
|
|
517
|
+
logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
|
|
518
|
+
keyId,
|
|
519
|
+
claimedOwnerId: claimedOwnerId.href
|
|
520
|
+
});
|
|
521
|
+
await keyCache?.set(cacheKey, null);
|
|
522
|
+
return {
|
|
523
|
+
key: null,
|
|
524
|
+
cached: false
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
370
529
|
if (keyCache != null) {
|
|
371
530
|
await keyCache.set(cacheKey, key);
|
|
372
531
|
logger.debug("Key {keyId} cached.", { keyId });
|
|
@@ -405,15 +564,18 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
|
|
|
405
564
|
}
|
|
406
565
|
logger.debug("Fetching key {keyId} to verify signature...", { keyId });
|
|
407
566
|
let document;
|
|
567
|
+
let documentUrl = cacheKey;
|
|
408
568
|
try {
|
|
409
|
-
|
|
569
|
+
const remoteDocument = await (options.documentLoader ?? getDocumentLoader())(keyId);
|
|
570
|
+
document = remoteDocument.document;
|
|
571
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", cacheKey);
|
|
410
572
|
} catch (error) {
|
|
411
573
|
const classified = classifyFetchError(error);
|
|
412
574
|
const errored = await onFetchError(error, cacheKey, keyId, keyCache, logger);
|
|
413
575
|
outcome = classified;
|
|
414
576
|
return errored;
|
|
415
577
|
}
|
|
416
|
-
const resolved = await resolveFetchedKey(document, cacheKey, keyId, cls, options, logger);
|
|
578
|
+
const resolved = await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
|
|
417
579
|
outcome = { result: resolved.key != null ? "fetched" : "invalid" };
|
|
418
580
|
return resolved;
|
|
419
581
|
} finally {
|
|
@@ -450,4 +612,4 @@ async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
|
450
612
|
});
|
|
451
613
|
}
|
|
452
614
|
//#endregion
|
|
453
|
-
export {
|
|
615
|
+
export { generateCryptoKeyPair as a, verifyKeyOwnership as c, fetchKeyDetailed as i, fetchActorDocument as n, importJwk as o, fetchKey as r, validateCryptoKey as s, exportJwk as t };
|
|
@@ -3,6 +3,7 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { CryptographicKey, Multikey } from "@fedify/vocab";
|
|
5
5
|
//#region src/federation/keycache.ts
|
|
6
|
+
const KEY_CACHE_GENERATION = "2";
|
|
6
7
|
var KvKeyCache = class {
|
|
7
8
|
kv;
|
|
8
9
|
prefix;
|
|
@@ -25,13 +26,20 @@ var KvKeyCache = class {
|
|
|
25
26
|
keyId.href
|
|
26
27
|
];
|
|
27
28
|
}
|
|
29
|
+
#entryKey(keyId) {
|
|
30
|
+
return [
|
|
31
|
+
...this.prefix,
|
|
32
|
+
KEY_CACHE_GENERATION,
|
|
33
|
+
keyId.href
|
|
34
|
+
];
|
|
35
|
+
}
|
|
28
36
|
async get(keyId) {
|
|
29
37
|
const negativeExpiration = this.nullKeys.get(keyId.href);
|
|
30
38
|
if (negativeExpiration != null) {
|
|
31
39
|
if (Temporal.Now.instant().until(negativeExpiration).sign >= 0) return null;
|
|
32
40
|
this.nullKeys.delete(keyId.href);
|
|
33
41
|
}
|
|
34
|
-
const serialized = await this.kv.get(
|
|
42
|
+
const serialized = await this.kv.get(this.#entryKey(keyId));
|
|
35
43
|
if (serialized === void 0) return void 0;
|
|
36
44
|
if (serialized === null) {
|
|
37
45
|
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
|
|
@@ -43,7 +51,7 @@ var KvKeyCache = class {
|
|
|
43
51
|
try {
|
|
44
52
|
return await Multikey.fromJsonLd(serialized, this.options);
|
|
45
53
|
} catch {
|
|
46
|
-
await this.kv.delete(
|
|
54
|
+
await this.kv.delete(this.#entryKey(keyId));
|
|
47
55
|
return;
|
|
48
56
|
}
|
|
49
57
|
}
|
|
@@ -51,12 +59,12 @@ var KvKeyCache = class {
|
|
|
51
59
|
async set(keyId, key) {
|
|
52
60
|
if (key == null) {
|
|
53
61
|
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
|
|
54
|
-
await this.kv.set(
|
|
62
|
+
await this.kv.set(this.#entryKey(keyId), null, { ttl: this.unavailableKeyTtl });
|
|
55
63
|
return;
|
|
56
64
|
}
|
|
57
65
|
this.nullKeys.delete(keyId.href);
|
|
58
66
|
const serialized = await key.toJsonLd(this.options);
|
|
59
|
-
await this.kv.set(
|
|
67
|
+
await this.kv.set(this.#entryKey(keyId), serialized, { ttl: this.keyTtl });
|
|
60
68
|
}
|
|
61
69
|
async getFetchError(keyId) {
|
|
62
70
|
const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
2
|
-
const require_http = require("./http-
|
|
2
|
+
const require_http = require("./http-Z3anvCd-.cjs");
|
|
3
3
|
let _logtape_logtape = require("@logtape/logtape");
|
|
4
|
-
let es_toolkit = require("es-toolkit");
|
|
5
4
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
5
|
+
let es_toolkit = require("es-toolkit");
|
|
6
6
|
//#region src/utils/docloader.ts
|
|
7
7
|
const logger$1 = (0, _logtape_logtape.getLogger)([
|
|
8
8
|
"fedify",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import {
|
|
3
|
+
import { T as recordDocumentCache, f as validateCryptoKey, t as doubleKnock } from "./http-CgwqtxAy.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
|
-
import { curry } from "es-toolkit";
|
|
6
5
|
import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
6
|
+
import { curry } from "es-toolkit";
|
|
7
7
|
//#region src/utils/docloader.ts
|
|
8
8
|
const logger$1 = getLogger([
|
|
9
9
|
"fedify",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { p as recordDocumentCache } from "./metrics-
|
|
4
|
+
import { p as recordDocumentCache } from "./metrics-CrqsHP0l.mjs";
|
|
5
5
|
import { getLogger } from "@logtape/logtape";
|
|
6
6
|
import { preloadedContexts } from "@fedify/vocab-runtime";
|
|
7
7
|
//#region src/utils/kv-cache.ts
|
|
@@ -1,9 +1,9 @@
|
|
|
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, s as measureSignatureKeyFetch } from "./metrics-
|
|
6
|
-
import {
|
|
4
|
+
import { n as version, t as name } from "./deno-BgMEefEr.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-CrqsHP0l.mjs";
|
|
6
|
+
import { r as fetchKey, s as validateCryptoKey } from "./key-Cwbg5ggV.mjs";
|
|
7
7
|
import { getLogger } from "@logtape/logtape";
|
|
8
8
|
import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
|
|
9
9
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
4
|
+
import { n as version, t as name } from "./deno-BgMEefEr.mjs";
|
|
5
5
|
import { metrics } from "@opentelemetry/api";
|
|
6
6
|
import { FetchError } from "@fedify/vocab-runtime";
|
|
7
7
|
//#region src/federation/metrics.ts
|