@fedify/fedify 2.4.0-pr.936.41 → 2.5.0-dev.2271
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/README.md +50 -39
- package/dist/{assert-CJ1KXcvv.mjs → assert-D1Eafmd_.mjs} +1 -1
- package/dist/{assert_equals-BV7oLMxm.mjs → assert_equals-Z6IcEbWU.mjs} +2 -28
- package/dist/assert_exists-cffSpJ7X.mjs +28 -0
- package/dist/assert_false-CPdRnc3E.mjs +23 -0
- package/dist/assert_greater_or_equal-BlafOF4-.mjs +31 -0
- package/dist/{assert_instance_of-Q9KTBnrf.mjs → assert_instance_of-mrXWWNY-.mjs} +1 -1
- package/dist/{assert_not_equals-EPJPoxuf.mjs → assert_not_equals-CpPK2t89.mjs} +2 -1
- package/dist/assert_rejects-CTptdYck.mjs +39 -0
- package/dist/{assert_strict_equals-Ec20AUwa.mjs → assert_strict_equals-CYvBmkpC.mjs} +2 -1
- package/dist/{assert_throws-NiFfqbnH.mjs → assert_throws-BYITOvPO.mjs} +2 -1
- package/dist/assertion_error-B-cx0fFA.mjs +31 -0
- package/dist/audience-CHQ-FlLD.mjs +95 -0
- package/dist/{builder-DonuxRl_.mjs → builder-Bda6LK7A.mjs} +169 -14
- package/dist/{circuit-breaker-CBGyz5F7.mjs → circuit-breaker-XJ53md4S.mjs} +224 -17
- package/dist/circuit-breaker-test-utils-DW6YC_Pe.mjs +14 -0
- package/dist/{client-CjF5IYQS.mjs → client-CEnNz8O7.mjs} +144 -8
- package/dist/{codec-BBga-FWI.mjs → codec-C6rTrpIp.mjs} +26 -3
- package/dist/{collection-BIhlldqK.mjs → collection-CnnP1UAv.mjs} +1 -1
- package/dist/compat/mod.cjs +1 -1
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/mod.js +1 -1
- package/dist/compat/outgoing-jsonld.test.mjs +71 -3
- package/dist/compat/public-audience.test.mjs +14 -5
- package/dist/compat/secured-document.test.d.mts +2 -0
- package/dist/compat/secured-document.test.mjs +58 -0
- package/dist/compat/transformers.test.mjs +5 -5
- package/dist/compound-proof-B-9mXxFW.mjs +989 -0
- package/dist/{context-Zii1f5xS.d.ts → context-7NyiqIcg.d.ts} +1600 -25
- package/dist/{context-BJXxPElm.mjs → context-8Tu9kea0.mjs} +21 -63
- package/dist/{context-lxX6I7as.d.cts → context-tsYPJ2Ew.d.cts} +1600 -25
- package/dist/{deno-BHzKn9Ig.mjs → deno-oR5aJKpU.mjs} +1 -1
- package/dist/dist-DJQM4u7i.mjs +189 -0
- package/dist/docloader-DBoYbvYW.mjs +87 -0
- package/dist/{esm-yYQfPhIY.mjs → esm-BHXXx4Wx.mjs} +13 -6
- package/dist/federation/audience.test.d.mts +2 -0
- package/dist/federation/audience.test.mjs +130 -0
- package/dist/federation/builder.test.mjs +54 -5
- package/dist/federation/circuit-breaker.test.mjs +838 -7
- package/dist/federation/collection.test.mjs +3 -3
- package/dist/federation/compound-fanout.test.d.mts +2 -0
- package/dist/federation/compound-fanout.test.mjs +228 -0
- package/dist/federation/handler-compound-proof.test.d.mts +2 -0
- package/dist/federation/handler-compound-proof.test.mjs +384 -0
- package/dist/federation/handler.test.mjs +385 -15
- package/dist/federation/hashlink-media.test.d.mts +2 -0
- package/dist/federation/hashlink-media.test.mjs +422 -0
- package/dist/federation/idempotency.test.mjs +6 -6
- package/dist/federation/inbox-report.test.d.mts +2 -0
- package/dist/federation/inbox-report.test.mjs +475 -0
- package/dist/federation/inbox.test.mjs +3 -3
- package/dist/federation/keycache.test.mjs +215 -14
- package/dist/federation/kv.test.mjs +3 -3
- package/dist/federation/metrics.test.mjs +5 -5
- package/dist/federation/middleware.test.mjs +967 -35
- package/dist/federation/mod.cjs +18 -261
- package/dist/federation/mod.d.cts +5 -5
- package/dist/federation/mod.d.ts +5 -5
- package/dist/federation/mod.js +279 -1
- package/dist/federation/mq.test.mjs +123 -47
- package/dist/federation/negotiation.test.mjs +4 -4
- package/dist/federation/outgoing-proof.test.d.mts +2 -0
- package/dist/federation/outgoing-proof.test.mjs +931 -0
- package/dist/federation/portable-actor.test.d.mts +2 -0
- package/dist/federation/portable-actor.test.mjs +496 -0
- package/dist/federation/portable-chain.test.d.mts +2 -0
- package/dist/federation/portable-chain.test.mjs +370 -0
- package/dist/federation/portable-collection.test.d.mts +2 -0
- package/dist/federation/portable-collection.test.mjs +832 -0
- package/dist/federation/portable-delivery.test.d.mts +2 -0
- package/dist/federation/portable-delivery.test.mjs +746 -0
- package/dist/federation/portable-gateway-keys.test.d.mts +2 -0
- package/dist/federation/portable-gateway-keys.test.mjs +686 -0
- package/dist/federation/portable-inbox.test.d.mts +2 -0
- package/dist/federation/portable-inbox.test.mjs +1535 -0
- package/dist/federation/portable-parse-uri.test.d.mts +2 -0
- package/dist/federation/portable-parse-uri.test.mjs +348 -0
- package/dist/federation/portable-route-activity.test.d.mts +2 -0
- package/dist/federation/portable-route-activity.test.mjs +259 -0
- package/dist/federation/portable.test.d.mts +2 -0
- package/dist/federation/portable.test.mjs +1072 -0
- package/dist/federation/retry.test.mjs +4 -3
- package/dist/federation/router.test.mjs +9 -159
- package/dist/federation/send.test.mjs +245 -27
- package/dist/federation/tasks/codec.test.mjs +4 -4
- package/dist/federation/tasks/enqueue.test.mjs +42 -4
- package/dist/federation/tasks/tasks.test.mjs +7 -6
- package/dist/federation/temporal.test.mjs +4 -4
- package/dist/federation/webfinger.test.mjs +275 -12
- package/dist/{middleware-FrSMAYEx.cjs → federation-CkZiAcWY.cjs} +8640 -3722
- package/dist/http-B5JT115n.cjs +10630 -0
- package/dist/http-DydWCB7R.js +6527 -0
- package/dist/{http-CPVMUgsW.mjs → http-siciWkmx.mjs} +397 -168
- package/dist/key-BspPiXqn.mjs +951 -0
- package/dist/key-C1nv7_jD.d.ts +1125 -0
- package/dist/key-DYxuyd-s.d.cts +1125 -0
- package/dist/key-owner-evidence-APB6NpSk.mjs +108 -0
- package/dist/keycache-Da8A8mZa.mjs +200 -0
- package/dist/{kv-D6hNiMTK.d.ts → kv-B2mzK5tl.d.cts} +3 -2
- package/dist/{kv-gJ8LYbxX.d.cts → kv-B2mzK5tl.d.ts} +3 -2
- package/dist/kv-DBRWVx3Q.mjs +104 -0
- package/dist/{kv-cache-KyGqJqSp.mjs → kv-cache-BF1hnW65.mjs} +1 -1
- package/dist/{kv-cache-5bnX3-Jq.js → kv-cache-CTQr1tbn.js} +54 -16
- package/dist/{kv-cache-DQ29C4XL.cjs → kv-cache-DCac1nEU.cjs} +55 -19
- package/dist/{ld-Cj9F_5AP.mjs → ld-CzaP-hJl.mjs} +44 -12
- package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
- package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
- package/dist/{metrics-DMk-nTxI.mjs → metrics-CYg9yxBL.mjs} +3 -2
- package/dist/metrics-DVKYY6K1.d.cts +36 -0
- package/dist/metrics-DoRtI0qU.d.ts +36 -0
- package/dist/{middleware-Y-kpsp6S.js → middleware-CsDDXKuL.js} +5493 -1103
- package/dist/{middleware-CvwTUgnP.mjs → middleware-D7_LO-Vs.mjs} +3157 -272
- package/dist/{middleware-CEctxBN6.mjs → middleware-DWjrfpcz.mjs} +1 -1
- package/dist/{mod-gOlPDuT8.d.cts → mod-BojP2Bw3.d.cts} +1 -1
- package/dist/mod-CavhAgLw.d.cts +320 -0
- package/dist/mod-CtpF2BU5.d.ts +320 -0
- package/dist/{mod-BtEaIEcD.d.cts → mod-DEZXwV6J.d.ts} +10 -4
- package/dist/{mod-UMmveiO0.d.ts → mod-Dw-nPxQh.d.cts} +10 -4
- package/dist/{mod-DLc2Ot6f.d.ts → mod-Xj5P4s4d.d.ts} +1 -1
- package/dist/mod.cjs +40 -39
- package/dist/mod.d.cts +10 -11
- package/dist/mod.d.ts +10 -11
- package/dist/mod.js +9 -9
- package/dist/{mq-llHKivST.mjs → mq-Dm1Wmtm-.mjs} +6 -0
- package/dist/nodeinfo/client.test.mjs +139 -4
- package/dist/nodeinfo/handler.test.mjs +5 -5
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.d.cts +1 -1
- package/dist/nodeinfo/mod.d.ts +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/nodeinfo/types.test.mjs +4 -4
- package/dist/otel/exporter.test.mjs +3 -3
- package/dist/otel/mod.cjs +0 -1
- package/dist/otel/mod.d.cts +1 -1
- package/dist/otel/mod.d.ts +1 -1
- package/dist/{outgoing-jsonld-URuqsICz.mjs → outgoing-jsonld-4IJqPYos.mjs} +12 -10
- package/dist/{owner-DEvZuyOE.d.cts → owner-BF0L9cMt.d.cts} +11 -0
- package/dist/{owner-CnngXDNJ.d.ts → owner-BSzr5-I8.d.ts} +11 -0
- package/dist/owner-DYgfTFHy.mjs +267 -0
- package/dist/portable-collection-BpsVsRov.js +670 -0
- package/dist/portable-collection-C74P1vtC.mjs +418 -0
- package/dist/portable-collection-DZd2bm7M.cjs +686 -0
- package/dist/portable-key-BIMLrAP4.mjs +549 -0
- package/dist/portable-key-id-Ca30-hOa.mjs +248 -0
- package/dist/proof-Dxy6ByqI.mjs +1299 -0
- package/dist/{public-audience-Cr5MsnMq.mjs → public-audience--klBIOk1.mjs} +15 -10
- package/dist/router-u5O5KCQr.mjs +156 -0
- package/dist/runtime/mod.cjs +1 -1
- package/dist/runtime/mod.js +1 -1
- package/dist/secured-document-CxzZZ9nL.mjs +35 -0
- package/dist/send-C4dDpAxm.mjs +495 -0
- package/dist/sig/accept.test.mjs +1 -1
- 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-producer.test.d.mts +2 -0
- package/dist/sig/compound-proof-producer.test.mjs +107 -0
- package/dist/sig/compound-proof-verification.test.d.mts +2 -0
- package/dist/sig/compound-proof-verification.test.mjs +833 -0
- package/dist/sig/compound-proof.test.d.mts +2 -0
- package/dist/sig/compound-proof.test.mjs +411 -0
- package/dist/sig/http.test.mjs +702 -31
- package/dist/sig/key-owner-evidence.test.d.mts +2 -0
- package/dist/sig/key-owner-evidence.test.mjs +100 -0
- package/dist/sig/key.test.mjs +220 -8
- package/dist/sig/ld.test.mjs +65 -7
- package/dist/sig/mitra-receive-interop.test.d.mts +2 -0
- package/dist/sig/mitra-receive-interop.test.mjs +260 -0
- package/dist/sig/mitra-send-interop.test.d.mts +2 -0
- package/dist/sig/mitra-send-interop.test.mjs +461 -0
- package/dist/sig/mod.cjs +18 -16
- package/dist/sig/mod.d.cts +4 -4
- package/dist/sig/mod.d.ts +4 -4
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.mjs +108 -9
- package/dist/sig/portable-collection.test.d.mts +2 -0
- package/dist/sig/portable-collection.test.mjs +697 -0
- package/dist/sig/portable-interop.test.d.mts +2 -0
- package/dist/sig/portable-interop.test.mjs +156 -0
- package/dist/sig/portable-key-id.test.d.mts +2 -0
- package/dist/sig/portable-key-id.test.mjs +67 -0
- package/dist/sig/portable-key.test.d.mts +2 -0
- package/dist/sig/portable-key.test.mjs +1439 -0
- package/dist/sig/proof.test.mjs +1753 -16
- package/dist/sig/tootik-request-interop.test.d.mts +2 -0
- package/dist/sig/tootik-request-interop.test.mjs +190 -0
- package/dist/sig/tootik-send-interop.test.d.mts +2 -0
- package/dist/sig/tootik-send-interop.test.mjs +331 -0
- package/dist/sig/verification.test.d.mts +2 -0
- package/dist/sig/verification.test.mjs +310 -0
- package/dist/std__assert-DvMDRPes.mjs +8 -0
- package/dist/{testing-CO7ftO8A.mjs → tasks-IG3pHfvZ.mjs} +2 -2
- package/dist/{temporal-fsXO5IvD.mjs → temporal-D0KJI4NF.mjs} +3 -3
- package/dist/testing/context.test.d.mts +2 -0
- package/dist/testing/context.test.mjs +19 -0
- package/dist/testing/kv.test.d.mts +2 -0
- package/dist/testing/kv.test.mjs +101 -0
- package/dist/testing/mod.d.mts +1617 -48
- package/dist/testing/mod.mjs +6 -3
- package/dist/testing-t8xDeJZ5.mjs +8 -0
- package/dist/{transformers-DGwl-hD1.cjs → transformers-BalCrSsr.cjs} +0 -1
- package/dist/{types-DC1pNgJA.js → types-_S2_MeLF.js} +144 -8
- package/dist/{types-CPym83zc.cjs → types-poIao40f.cjs} +160 -7
- package/dist/utils/docloader.test.mjs +530 -8
- package/dist/utils/kv-cache.test.mjs +2 -2
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/dist/verification-C3954hQh.mjs +5 -0
- package/dist/verification-Dpfcw8ml.mjs +105 -0
- package/dist/vocab/cjs.test.mjs +1 -1
- package/package.json +8 -8
- package/dist/assert_rejects-EgstcQy5.mjs +0 -82
- package/dist/docloader-e1khNUKy.mjs +0 -49
- package/dist/http-CsmQ-BQ4.js +0 -2483
- package/dist/http-VyDTd4G3.d.cts +0 -553
- package/dist/http-hlIjmewS.cjs +0 -2694
- package/dist/http-lf8Hsd91.d.ts +0 -553
- package/dist/key-DfPsX8hv.mjs +0 -453
- package/dist/keycache-_DHqz5z_.mjs +0 -97
- package/dist/metrics-CoUJLQhZ.d.cts +0 -276
- package/dist/metrics-Dc4jR-IC.d.ts +0 -276
- package/dist/mod-B0hW12_O.d.cts +0 -307
- package/dist/mod-COIAjwRS.d.ts +0 -307
- package/dist/owner-Br9YYzn_.mjs +0 -120
- package/dist/proof-B4ngoaNQ.mjs +0 -316
- package/dist/proof-B6E0XUXJ.js +0 -1415
- package/dist/proof-NN4xCQGL.cjs +0 -1562
- package/dist/send-CxHKUNzv.mjs +0 -239
- package/dist/std__assert-B2NnY9bq.mjs +0 -79
- package/dist/temporal-polyfill-BCdTHexd.cjs +0 -3565
- /package/dist/{accept-8PiolDP7.mjs → accept-CPVqki78.mjs} +0 -0
- /package/dist/{activity-listener-BtTSxZWA.mjs → activity-listener-C-BSbKI7.mjs} +0 -0
- /package/dist/{client-CAM_bQXx.d.cts → client-D-XP_Ccb.d.cts} +0 -0
- /package/dist/{client-CSddvgWN.d.ts → client-D-XP_Ccb.d.ts} +0 -0
- /package/dist/{keys-Dbyd7bhl.mjs → keys-DIJNHW-Y.mjs} +0 -0
- /package/dist/{kv-CXBAok3k.mjs → kv-DCHkaJ9-.mjs} +0 -0
- /package/dist/{negotiation-RuwrPFqi.mjs → negotiation-CCocWT_N.mjs} +0 -0
- /package/dist/{retry-DgFqeS5k.mjs → retry-zX5xw5e0.mjs} +0 -0
- /package/dist/{chunk-D5IdezsZ.js → rolldown-runtime-BC-Eo5Mj.js} +0 -0
- /package/dist/{chunk-Czpeue4M.cjs → rolldown-runtime-Dmm3t92W.cjs} +0 -0
- /package/dist/{transformers-DXze6FUB.js → transformers-DAtXl_Kc.js} +0 -0
- /package/dist/{types-CrtdVuDP.mjs → types-DE_LV1JB.mjs} +0 -0
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
import { Temporal } from "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { c as isPortableActorDocument, d as isPortableUri, l as isPortableId, m as parseKeyIdString, s as isCompatibleKeyId } from "./portable-key-id-Ca30-hOa.mjs";
|
|
5
|
+
import { a as verificationObservation } from "./verification-Dpfcw8ml.mjs";
|
|
6
|
+
import { n as version, t as name } from "./deno-oR5aJKpU.mjs";
|
|
7
|
+
import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-CYg9yxBL.mjs";
|
|
8
|
+
import { n as attachKeyOwnerEvidence, t as MAX_PORTABLE_KEY_TTL } from "./key-owner-evidence-APB6NpSk.mjs";
|
|
9
|
+
import { getLogger } from "@logtape/logtape";
|
|
10
|
+
import { CryptographicKey, Multikey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
11
|
+
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
12
|
+
import { FetchError, getDocumentLoader, parseDidKeyVerificationMethod } from "@fedify/vocab-runtime";
|
|
13
|
+
//#region src/sig/key.ts
|
|
14
|
+
/**
|
|
15
|
+
* Checks if the given key is valid and supported. No-op if the key is valid,
|
|
16
|
+
* otherwise throws an error.
|
|
17
|
+
* @param key The key to check.
|
|
18
|
+
* @param type Which type of key to check. If not specified, the key can be
|
|
19
|
+
* either public or private.
|
|
20
|
+
* @throws {TypeError} If the key is invalid or unsupported.
|
|
21
|
+
*/
|
|
22
|
+
function validateCryptoKey(key, type) {
|
|
23
|
+
if (type != null && key.type !== type) throw new TypeError(`The key is not a ${type} key.`);
|
|
24
|
+
if (!key.extractable) throw new TypeError("The key is not extractable.");
|
|
25
|
+
if (key.algorithm.name !== "RSASSA-PKCS1-v1_5" && key.algorithm.name !== "Ed25519") throw new TypeError("Currently only RSASSA-PKCS1-v1_5 and Ed25519 keys are supported. More algorithms will be added in the future!");
|
|
26
|
+
if (key.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
27
|
+
if (key.algorithm.hash.name !== "SHA-256") throw new TypeError("For compatibility with the existing Fediverse software (e.g., Mastodon), hash algorithm for RSASSA-PKCS1-v1_5 keys must be SHA-256.");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Generates a key pair which is appropriate for Fedify.
|
|
32
|
+
* @param algorithm The algorithm to use. Currently only RSASSA-PKCS1-v1_5 and
|
|
33
|
+
* Ed25519 are supported.
|
|
34
|
+
* @returns The generated key pair.
|
|
35
|
+
* @throws {TypeError} If the algorithm is unsupported.
|
|
36
|
+
*/
|
|
37
|
+
function generateCryptoKeyPair(algorithm) {
|
|
38
|
+
if (algorithm == null) getLogger([
|
|
39
|
+
"fedify",
|
|
40
|
+
"sig",
|
|
41
|
+
"key"
|
|
42
|
+
]).warn("No algorithm specified. Using RSASSA-PKCS1-v1_5 by default, but it is recommended to specify the algorithm explicitly as the parameter will be required in the future.");
|
|
43
|
+
if (algorithm == null || algorithm === "RSASSA-PKCS1-v1_5") return crypto.subtle.generateKey({
|
|
44
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
45
|
+
modulusLength: 4096,
|
|
46
|
+
publicExponent: new Uint8Array([
|
|
47
|
+
1,
|
|
48
|
+
0,
|
|
49
|
+
1
|
|
50
|
+
]),
|
|
51
|
+
hash: "SHA-256"
|
|
52
|
+
}, true, ["sign", "verify"]);
|
|
53
|
+
else if (algorithm === "Ed25519") return crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
|
|
54
|
+
throw new TypeError("Unsupported algorithm: " + algorithm);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Exports a key in JWK format.
|
|
58
|
+
* @param key The key to export. Either public or private key.
|
|
59
|
+
* @returns The exported key in JWK format. The key is suitable for
|
|
60
|
+
* serialization and storage.
|
|
61
|
+
* @throws {TypeError} If the key is invalid or unsupported.
|
|
62
|
+
*/
|
|
63
|
+
async function exportJwk(key) {
|
|
64
|
+
validateCryptoKey(key);
|
|
65
|
+
const jwk = await crypto.subtle.exportKey("jwk", key);
|
|
66
|
+
if (jwk.crv === "Ed25519") jwk.alg = "Ed25519";
|
|
67
|
+
return jwk;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Imports a key from JWK format.
|
|
71
|
+
* @param jwk The key in JWK format.
|
|
72
|
+
* @param type Which type of key to import, either `"public"` or `"private"`.
|
|
73
|
+
* @returns The imported key.
|
|
74
|
+
* @throws {TypeError} If the key is invalid or unsupported.
|
|
75
|
+
*/
|
|
76
|
+
async function importJwk(jwk, type) {
|
|
77
|
+
let key;
|
|
78
|
+
if (jwk.kty === "RSA" && jwk.alg === "RS256") key = await crypto.subtle.importKey("jwk", jwk, {
|
|
79
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
80
|
+
hash: "SHA-256"
|
|
81
|
+
}, true, type === "public" ? ["verify"] : ["sign"]);
|
|
82
|
+
else if (jwk.kty === "OKP" && jwk.crv === "Ed25519") {
|
|
83
|
+
if (navigator?.userAgent === "Cloudflare-Workers") {
|
|
84
|
+
jwk = { ...jwk };
|
|
85
|
+
delete jwk.alg;
|
|
86
|
+
}
|
|
87
|
+
key = await crypto.subtle.importKey("jwk", jwk, "Ed25519", true, type === "public" ? ["verify"] : ["sign"]);
|
|
88
|
+
} else throw new TypeError("Unsupported JWK format.");
|
|
89
|
+
validateCryptoKey(key, type);
|
|
90
|
+
return key;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Fetches the document that the given actor URI dereferences to, and returns
|
|
94
|
+
* the actor only if the host that served the document is authoritative for
|
|
95
|
+
* the id the document claims.
|
|
96
|
+
*
|
|
97
|
+
* Only the origin that serves an actor id can speak for it. Without that
|
|
98
|
+
* rule any host could serve a document describing somebody else's actor—and
|
|
99
|
+
* listing its own keys as that actor's.
|
|
100
|
+
*
|
|
101
|
+
* @param actorId The URI of the actor to fetch.
|
|
102
|
+
* @param options Options for fetching the document.
|
|
103
|
+
* @returns The actor, or `null` if the document cannot be fetched, is not an
|
|
104
|
+
* actor, or belongs to another origin.
|
|
105
|
+
* @internal
|
|
106
|
+
*/
|
|
107
|
+
async function fetchActorDocument(actorId, options = {}) {
|
|
108
|
+
const logger = getLogger([
|
|
109
|
+
"fedify",
|
|
110
|
+
"sig",
|
|
111
|
+
"key"
|
|
112
|
+
]);
|
|
113
|
+
if (isPortableId(actorId)) {
|
|
114
|
+
logger.debug("The actor {actorId} is a portable actor, which is not authenticated by the web origin that serves it.", { actorId: actorId.href });
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const documentLoader = options.documentLoader ?? getDocumentLoader();
|
|
118
|
+
const contextLoader = options.contextLoader ?? getDocumentLoader();
|
|
119
|
+
const { tracerProvider } = options;
|
|
120
|
+
let document;
|
|
121
|
+
let documentUrl = actorId;
|
|
122
|
+
try {
|
|
123
|
+
const remoteDocument = await documentLoader(actorId.href);
|
|
124
|
+
document = remoteDocument.document;
|
|
125
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", actorId);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
logger.debug("Failed to fetch the actor {actorId}: {error}", {
|
|
128
|
+
actorId: actorId.href,
|
|
129
|
+
error
|
|
130
|
+
});
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
let object;
|
|
134
|
+
try {
|
|
135
|
+
object = await Object$1.fromJsonLd(document, {
|
|
136
|
+
documentLoader,
|
|
137
|
+
contextLoader,
|
|
138
|
+
tracerProvider,
|
|
139
|
+
baseUrl: documentUrl
|
|
140
|
+
});
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (!(error instanceof TypeError)) throw error;
|
|
143
|
+
logger.debug("The document served at {documentUrl} is not a valid object: {error}", {
|
|
144
|
+
documentUrl: documentUrl.href,
|
|
145
|
+
error
|
|
146
|
+
});
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
if (!isActor(object)) return null;
|
|
150
|
+
if (object.id != null && isPortableId(object.id)) {
|
|
151
|
+
logger.debug("The document served at {documentUrl} claims to be the portable actor {actorId}, which is not authenticated by the web origin that serves it.", {
|
|
152
|
+
documentUrl: documentUrl.href,
|
|
153
|
+
actorId: object.id?.href
|
|
154
|
+
});
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
158
|
+
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.", {
|
|
159
|
+
documentUrl: documentUrl.href,
|
|
160
|
+
actorId: object.id?.href
|
|
161
|
+
});
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
return object;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Resolves the owner that the given key claims, and returns it only if the
|
|
168
|
+
* claim holds.
|
|
169
|
+
*
|
|
170
|
+
* The `owner` of a {@link CryptographicKey}, like the `controller` of
|
|
171
|
+
* a {@link Multikey}, proves nothing on its own: the key document and the
|
|
172
|
+
* claim inside it are served by the same host, so anyone able to serve a key
|
|
173
|
+
* document can name any actor in the world as its owner. The claim becomes
|
|
174
|
+
* meaningful only once the named actor's own document is fetched and turns
|
|
175
|
+
* out to link back to the key. This function performs that mutual-link
|
|
176
|
+
* check, which is the only sound way to learn a fetched key's owner.
|
|
177
|
+
*
|
|
178
|
+
* The claimed owner is always dereferenced, never read out of the key
|
|
179
|
+
* document: an owner embedded there is written by whoever wrote the key.
|
|
180
|
+
*
|
|
181
|
+
* @param key The key whose ownership claim is to be verified. It must carry
|
|
182
|
+
* an `id`, as that is what the owner has to link back to.
|
|
183
|
+
* @param options Options for fetching the claimed owner.
|
|
184
|
+
* @returns The verified owner, or `null` if the key claims no owner, the
|
|
185
|
+
* owner cannot be fetched, or the owner does not link back to
|
|
186
|
+
* the key.
|
|
187
|
+
* @internal
|
|
188
|
+
*/
|
|
189
|
+
async function verifyKeyOwnership(key, options = {}) {
|
|
190
|
+
const logger = getLogger([
|
|
191
|
+
"fedify",
|
|
192
|
+
"sig",
|
|
193
|
+
"key"
|
|
194
|
+
]);
|
|
195
|
+
const keyId = key.id;
|
|
196
|
+
if (keyId == null) return null;
|
|
197
|
+
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
198
|
+
if (claimedOwnerId == null) return null;
|
|
199
|
+
const owner = await fetchActorDocument(claimedOwnerId, options);
|
|
200
|
+
if (owner == null) {
|
|
201
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
|
|
202
|
+
keyId: keyId.href,
|
|
203
|
+
claimedOwnerId: claimedOwnerId.href
|
|
204
|
+
});
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const linkedKeyIds = key instanceof CryptographicKey ? owner.publicKeyIds : owner.assertionMethodIds;
|
|
208
|
+
for (const linkedKeyId of linkedKeyIds) if (linkedKeyId.href === keyId.href) return owner;
|
|
209
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims does not list the key as its own.", {
|
|
210
|
+
keyId: keyId.href,
|
|
211
|
+
claimedOwnerId: claimedOwnerId.href
|
|
212
|
+
});
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Wraps a key cache so that lookups through the wrapper miss, while
|
|
217
|
+
* whatever they resolve is still written to the cache. It is used to retry
|
|
218
|
+
* a verification that failed with a cached key with a freshly fetched one.
|
|
219
|
+
* @param keyCache The key cache to wrap.
|
|
220
|
+
* @returns The wrapped key cache.
|
|
221
|
+
* @internal
|
|
222
|
+
*/
|
|
223
|
+
function bypassKeyCacheReads(keyCache) {
|
|
224
|
+
const cache = keyCache;
|
|
225
|
+
const bypassed = {
|
|
226
|
+
get: () => Promise.resolve(void 0),
|
|
227
|
+
set: async (keyId, key) => await cache?.set(keyId, key),
|
|
228
|
+
getFetchError: () => Promise.resolve(void 0),
|
|
229
|
+
setFetchError: async (keyId, error) => await cache?.setFetchError?.(keyId, error)
|
|
230
|
+
};
|
|
231
|
+
if (cache?.compatibleKeyScope != null) bypassed.compatibleKeyScope = (scope) => {
|
|
232
|
+
const scoped = cache.compatibleKeyScope(scope);
|
|
233
|
+
return {
|
|
234
|
+
get: () => Promise.resolve(void 0),
|
|
235
|
+
getEntry: () => Promise.resolve(void 0),
|
|
236
|
+
set: async (keyId, key, options) => await scoped.set(keyId, key, options),
|
|
237
|
+
delete: async (keyId) => await scoped.delete(keyId)
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
return bypassed;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Parses a key ID given as a string. Unlike `new URL()`, it accepts `ap:`
|
|
244
|
+
* and `ap+ef61:` URIs, and refuses a portable or compatible key ID whose
|
|
245
|
+
* identity URL parsing would change, e.g., by resolving dot segments.
|
|
246
|
+
* @throws {TypeError} If the key ID is invalid.
|
|
247
|
+
*/
|
|
248
|
+
function parseKeyIdOrThrow(keyId) {
|
|
249
|
+
const parsed = parseKeyIdString(keyId);
|
|
250
|
+
if (parsed == null) throw new TypeError(`Invalid key ID: ${keyId}`);
|
|
251
|
+
return parsed;
|
|
252
|
+
}
|
|
253
|
+
async function withFetchKeySpan(keyId, tracerProvider, fetcher) {
|
|
254
|
+
tracerProvider ??= trace.getTracerProvider();
|
|
255
|
+
return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.fetch_key", {
|
|
256
|
+
kind: SpanKind.CLIENT,
|
|
257
|
+
attributes: {
|
|
258
|
+
"http.method": "GET",
|
|
259
|
+
"url.full": keyId.href,
|
|
260
|
+
"url.scheme": keyId.protocol.replace(/:$/, ""),
|
|
261
|
+
"url.domain": keyId.hostname,
|
|
262
|
+
"url.path": keyId.pathname,
|
|
263
|
+
"url.query": keyId.search.replace(/^\?/, ""),
|
|
264
|
+
"url.fragment": keyId.hash.replace(/^#/, "")
|
|
265
|
+
}
|
|
266
|
+
}, async (span) => {
|
|
267
|
+
try {
|
|
268
|
+
const result = await fetcher();
|
|
269
|
+
span.setAttribute("activitypub.actor.key.cached", result.cached);
|
|
270
|
+
return result;
|
|
271
|
+
} catch (e) {
|
|
272
|
+
span.setStatus({
|
|
273
|
+
code: SpanStatusCode.ERROR,
|
|
274
|
+
message: String(e)
|
|
275
|
+
});
|
|
276
|
+
throw e;
|
|
277
|
+
} finally {
|
|
278
|
+
span.end();
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Fetches a {@link CryptographicKey} or {@link Multikey} from the given URL.
|
|
284
|
+
* If the given URL contains an {@link Actor} object, it tries to find
|
|
285
|
+
* the corresponding key in the `publicKey` or `assertionMethod` property. *
|
|
286
|
+
* Gateway keys of [FEP-ef61] portable actors, i.e., keys whose IDs are
|
|
287
|
+
* compatible identifiers that dereference to portable actor documents, are
|
|
288
|
+
* not resolved by this function, as they only authenticate HTTP requests;
|
|
289
|
+
* use `verifyRequest()` or `getKeyOwner()` for them instead. Nor are keys
|
|
290
|
+
* of portable actors whose IDs are `ap:` or `ap+ef61:` URIs, for the same
|
|
291
|
+
* reason. Keys whose IDs are compatible identifiers or `ap:` or `ap+ef61:`
|
|
292
|
+
* URIs are never read from or written to the key cache under their IDs,
|
|
293
|
+
* except for failures to fetch them.
|
|
294
|
+
*
|
|
295
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
296
|
+
* @template T The type of the key to fetch. Either {@link CryptographicKey}
|
|
297
|
+
* or {@link Multikey}.
|
|
298
|
+
* @param keyId The URL of the key.
|
|
299
|
+
* @param cls The class of the key to fetch. Either {@link CryptographicKey}
|
|
300
|
+
* or {@link Multikey}.
|
|
301
|
+
* @param options Options for fetching the key. See {@link FetchKeyOptions}.
|
|
302
|
+
* @returns The fetched key or `null` if the key is not found.
|
|
303
|
+
* @since 1.3.0
|
|
304
|
+
*/
|
|
305
|
+
function fetchKey(keyId, cls, options = {}) {
|
|
306
|
+
keyId = typeof keyId === "string" ? parseKeyIdOrThrow(keyId) : keyId;
|
|
307
|
+
return withFetchKeySpan(keyId, options.tracerProvider, () => fetchKeyInternal(keyId, cls, options));
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Fetches a {@link CryptographicKey} or {@link Multikey} from the given URL,
|
|
311
|
+
* preserving transport-level fetch failures for callers that need to inspect
|
|
312
|
+
* why the key could not be loaded. *
|
|
313
|
+
* Gateway keys of [FEP-ef61] portable actors, i.e., keys whose IDs are
|
|
314
|
+
* compatible identifiers that dereference to portable actor documents, are
|
|
315
|
+
* not resolved by this function, as they only authenticate HTTP requests;
|
|
316
|
+
* use `verifyRequest()` or `getKeyOwner()` for them instead. Nor are keys
|
|
317
|
+
* of portable actors whose IDs are `ap:` or `ap+ef61:` URIs, for the same
|
|
318
|
+
* reason. Keys whose IDs are compatible identifiers or `ap:` or `ap+ef61:`
|
|
319
|
+
* URIs are never read from or written to the key cache under their IDs,
|
|
320
|
+
* except for failures to fetch them.
|
|
321
|
+
*
|
|
322
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
323
|
+
*
|
|
324
|
+
* @template T The type of the key to fetch. Either {@link CryptographicKey}
|
|
325
|
+
* or {@link Multikey}.
|
|
326
|
+
* @param keyId The URL of the key.
|
|
327
|
+
* @param cls The class of the key to fetch. Either {@link CryptographicKey}
|
|
328
|
+
* or {@link Multikey}.
|
|
329
|
+
* @param options Options for fetching the key.
|
|
330
|
+
* @returns The fetched key, or detailed fetch failure information.
|
|
331
|
+
* @since 2.1.0
|
|
332
|
+
*/
|
|
333
|
+
async function fetchKeyDetailed(keyId, cls, options = {}) {
|
|
334
|
+
const cacheKey = typeof keyId === "string" ? parseKeyIdOrThrow(keyId) : keyId;
|
|
335
|
+
return await withFetchKeySpan(cacheKey, options.tracerProvider, async () => {
|
|
336
|
+
return await fetchKeyWithResult(cacheKey, cls, options, async (cacheKey, keyId, keyCache, logger) => {
|
|
337
|
+
const fetchError = await keyCache?.getFetchError?.(cacheKey);
|
|
338
|
+
if (fetchError != null) {
|
|
339
|
+
logger.debug("Entry {keyId} found in cache with preserved fetch failure details.", { keyId });
|
|
340
|
+
return {
|
|
341
|
+
key: null,
|
|
342
|
+
cached: true,
|
|
343
|
+
fetchError
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
logger.debug("Entry {keyId} found in cache, but no fetch failure details are available.", { keyId });
|
|
347
|
+
return {
|
|
348
|
+
key: null,
|
|
349
|
+
cached: true
|
|
350
|
+
};
|
|
351
|
+
}, async (error, cacheKey, keyId, keyCache, logger) => {
|
|
352
|
+
logger.debug("Failed to fetch key {keyId}.", {
|
|
353
|
+
keyId,
|
|
354
|
+
error
|
|
355
|
+
});
|
|
356
|
+
await keyCache?.set(cacheKey, null);
|
|
357
|
+
if (error instanceof FetchError && error.response != null) {
|
|
358
|
+
const fetchError = {
|
|
359
|
+
status: error.response.status,
|
|
360
|
+
response: error.response.clone()
|
|
361
|
+
};
|
|
362
|
+
await keyCache?.setFetchError?.(cacheKey, fetchError);
|
|
363
|
+
return {
|
|
364
|
+
key: null,
|
|
365
|
+
cached: false,
|
|
366
|
+
fetchError
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
const fetchError = { error: error instanceof Error ? error : new Error(String(error)) };
|
|
370
|
+
await keyCache?.setFetchError?.(cacheKey, fetchError);
|
|
371
|
+
return {
|
|
372
|
+
key: null,
|
|
373
|
+
cached: false,
|
|
374
|
+
fetchError
|
|
375
|
+
};
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
async function getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger) {
|
|
380
|
+
if (keyCache == null) return null;
|
|
381
|
+
const cachedKey = await keyCache.get(cacheKey);
|
|
382
|
+
const hit = checkCachedKeyHit(cachedKey, keyId, cls, logger);
|
|
383
|
+
if (hit != null) return hit;
|
|
384
|
+
if (cachedKey === null) {
|
|
385
|
+
logger.debug("Entry {keyId} found in cache, but it is unavailable.", { keyId });
|
|
386
|
+
return {
|
|
387
|
+
key: null,
|
|
388
|
+
cached: true
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
function checkCachedKeyHit(cachedKey, keyId, cls, logger) {
|
|
394
|
+
if (cachedKey instanceof cls && cachedKey.publicKey != null) {
|
|
395
|
+
logger.debug("Key {keyId} found in cache.", { keyId });
|
|
396
|
+
return {
|
|
397
|
+
key: cachedKey,
|
|
398
|
+
cached: true
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Looks up a key at a compatible identifier, or at an `ap:` or `ap+ef61:`
|
|
405
|
+
* URI, in the cache namespace of a purpose, like {@link getCachedFetchKey}.
|
|
406
|
+
* A key that the verified document of its portable actor was cached with
|
|
407
|
+
* gets the actor's evidence attached from the same entry; see
|
|
408
|
+
* {@link attachKeyOwnerEvidence}.
|
|
409
|
+
*/
|
|
410
|
+
async function getCachedCompatibleKey(cacheKey, keyId, cls, scopedCache, logger) {
|
|
411
|
+
if (scopedCache?.getEntry == null) return await getCachedFetchKey(cacheKey, keyId, cls, scopedCache, logger);
|
|
412
|
+
const entry = await scopedCache.getEntry(cacheKey);
|
|
413
|
+
if (entry === void 0) return null;
|
|
414
|
+
if (entry === null) {
|
|
415
|
+
logger.debug("Entry {keyId} found in cache, but it is unavailable.", { keyId });
|
|
416
|
+
return {
|
|
417
|
+
key: null,
|
|
418
|
+
cached: true
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
const hit = checkCachedKeyHit(entry.key, keyId, cls, logger);
|
|
422
|
+
if (hit?.key instanceof CryptographicKey && entry.owner != null) attachKeyOwnerEvidence(hit.key, entry.owner, entry.expires);
|
|
423
|
+
return hit;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Attaches the evidence of the portable actor whose document has just
|
|
427
|
+
* vouched for a key to the key. The evidence expires with the cache entry
|
|
428
|
+
* of the key, so that it is never trusted longer than the key is cached.
|
|
429
|
+
* Without a cache, it expires with the proof of the document, but no later
|
|
430
|
+
* than {@link MAX_PORTABLE_KEY_TTL}, as a cache entry would.
|
|
431
|
+
* @param resolution The resolution of the key.
|
|
432
|
+
* @param scopedCache The cache namespace the key was stored in, if any.
|
|
433
|
+
* @param cached When the cache entry expires, if the cache stored one.
|
|
434
|
+
*/
|
|
435
|
+
function attachResolvedKeyOwnerEvidence(resolution, scopedCache, cached) {
|
|
436
|
+
let expires;
|
|
437
|
+
if (scopedCache != null) {
|
|
438
|
+
if (cached == null) return;
|
|
439
|
+
expires = cached;
|
|
440
|
+
} else {
|
|
441
|
+
expires = Temporal.Now.instant().add(MAX_PORTABLE_KEY_TTL);
|
|
442
|
+
if (resolution.expires != null && Temporal.Instant.compare(resolution.expires, expires) < 0) expires = resolution.expires;
|
|
443
|
+
}
|
|
444
|
+
attachKeyOwnerEvidence(resolution.key, resolution.document, expires);
|
|
445
|
+
}
|
|
446
|
+
async function clearFetchErrorMetadata(keyId, keyCache) {
|
|
447
|
+
await keyCache?.setFetchError?.(keyId, null);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Returns the identifier a fragment-bearing DID URL is a fragment of, or
|
|
451
|
+
* `null` if the URL is not a DID URL. `did:web:example.com#key` is thereby
|
|
452
|
+
* traced back to `did:web:example.com`, the DID that resolving the key went
|
|
453
|
+
* through in the first place.
|
|
454
|
+
*/
|
|
455
|
+
function stripFragment(keyId) {
|
|
456
|
+
if (keyId.protocol !== "did:") return null;
|
|
457
|
+
const did = new URL(keyId.href);
|
|
458
|
+
did.hash = "";
|
|
459
|
+
return did;
|
|
460
|
+
}
|
|
461
|
+
function isDidKeyUrl(keyId) {
|
|
462
|
+
return keyId.protocol === "did:" && keyId.pathname.startsWith("key:");
|
|
463
|
+
}
|
|
464
|
+
async function doRawKeysMatch(left, right) {
|
|
465
|
+
const [leftRaw, rightRaw] = await Promise.all([crypto.subtle.exportKey("raw", left), crypto.subtle.exportKey("raw", right)]);
|
|
466
|
+
if (leftRaw.byteLength !== rightRaw.byteLength) return false;
|
|
467
|
+
const leftBytes = new Uint8Array(leftRaw);
|
|
468
|
+
const rightBytes = new Uint8Array(rightRaw);
|
|
469
|
+
return leftBytes.every((byte, index) => byte === rightBytes[index]);
|
|
470
|
+
}
|
|
471
|
+
async function isCachedDidKeyValid(key, verificationMethod) {
|
|
472
|
+
try {
|
|
473
|
+
return key.id?.href === verificationMethod.id.href && key.controllerId?.href === verificationMethod.controller.href && await doRawKeysMatch(key.publicKey, verificationMethod.publicKey);
|
|
474
|
+
} catch {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async function resolveDidKey(cacheKey, cls, keyCache, logger) {
|
|
479
|
+
if (!isDidKeyUrl(cacheKey)) return null;
|
|
480
|
+
const keyId = cacheKey.href;
|
|
481
|
+
if (!(cls === Multikey)) {
|
|
482
|
+
logger.debug("Failed to resolve did:key {keyId}; did:key verification methods are only supported as Multikey values.", { keyId });
|
|
483
|
+
return {
|
|
484
|
+
key: null,
|
|
485
|
+
cached: false
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
let verificationMethod;
|
|
489
|
+
try {
|
|
490
|
+
verificationMethod = await parseDidKeyVerificationMethod(cacheKey);
|
|
491
|
+
} catch (error) {
|
|
492
|
+
logger.debug("Failed to resolve did:key verification method {keyId}: {error}", {
|
|
493
|
+
keyId,
|
|
494
|
+
error
|
|
495
|
+
});
|
|
496
|
+
return {
|
|
497
|
+
key: null,
|
|
498
|
+
cached: false
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
const hit = checkCachedKeyHit(await keyCache?.get(cacheKey), keyId, cls, logger);
|
|
502
|
+
if (hit?.key instanceof Multikey && await isCachedDidKeyValid(hit.key, verificationMethod)) return hit;
|
|
503
|
+
const key = new Multikey({
|
|
504
|
+
id: verificationMethod.id,
|
|
505
|
+
controller: verificationMethod.controller,
|
|
506
|
+
publicKey: verificationMethod.publicKey
|
|
507
|
+
});
|
|
508
|
+
await keyCache?.set(cacheKey, key);
|
|
509
|
+
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
510
|
+
logger.debug("Resolved did:key verification method {keyId}.", { keyId });
|
|
511
|
+
return {
|
|
512
|
+
key,
|
|
513
|
+
cached: false
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider, keyIdBoundByCaller, portableKeyResolvers }, logger) {
|
|
517
|
+
let object;
|
|
518
|
+
try {
|
|
519
|
+
object = await Object$1.fromJsonLd(document, {
|
|
520
|
+
documentLoader,
|
|
521
|
+
contextLoader,
|
|
522
|
+
tracerProvider
|
|
523
|
+
});
|
|
524
|
+
} catch (e) {
|
|
525
|
+
if (!(e instanceof TypeError)) throw e;
|
|
526
|
+
try {
|
|
527
|
+
object = await cls.fromJsonLd(document, {
|
|
528
|
+
documentLoader,
|
|
529
|
+
contextLoader,
|
|
530
|
+
tracerProvider
|
|
531
|
+
});
|
|
532
|
+
} catch (e) {
|
|
533
|
+
if (e instanceof TypeError) {
|
|
534
|
+
logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
|
|
535
|
+
await keyCache?.set(cacheKey, null);
|
|
536
|
+
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
537
|
+
return {
|
|
538
|
+
key: null,
|
|
539
|
+
cached: false
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
throw e;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (isCompatibleKeyId(cacheKey) && isPortableActorDocument(object)) {
|
|
546
|
+
const scopedCache = keyCache;
|
|
547
|
+
if (portableKeyResolvers == null || cls !== CryptographicKey) {
|
|
548
|
+
logger.debug("Failed to verify; key {keyId} is a gateway key of the portable actor {actorId}, which is only accepted for HTTP Signatures.", {
|
|
549
|
+
keyId,
|
|
550
|
+
actorId: object.id?.href
|
|
551
|
+
});
|
|
552
|
+
await scopedCache?.set(cacheKey, null);
|
|
553
|
+
return {
|
|
554
|
+
key: null,
|
|
555
|
+
cached: false
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
const resolution = await portableKeyResolvers.gatewayKey(document, object, cacheKey);
|
|
559
|
+
if (resolution.type !== "verified") {
|
|
560
|
+
logger.debug("Failed to verify; the portable actor {actorId} does not vouch for its gateway key {keyId}.", {
|
|
561
|
+
keyId,
|
|
562
|
+
actorId: object.id?.href
|
|
563
|
+
});
|
|
564
|
+
await scopedCache?.set(cacheKey, null);
|
|
565
|
+
return {
|
|
566
|
+
key: null,
|
|
567
|
+
cached: false
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
attachResolvedKeyOwnerEvidence(resolution, scopedCache, await scopedCache?.set(cacheKey, resolution.key, {
|
|
571
|
+
expires: resolution.expires,
|
|
572
|
+
owner: resolution.document
|
|
573
|
+
}));
|
|
574
|
+
return {
|
|
575
|
+
key: resolution.key,
|
|
576
|
+
cached: false
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
let key = null;
|
|
580
|
+
let ownerDocument = null;
|
|
581
|
+
if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object.id == null ? object.clone({ id: cacheKey }) : object;
|
|
582
|
+
else if (isActor(object)) {
|
|
583
|
+
if (object.id == null || isPortableId(object.id) || object.id.origin !== documentUrl.origin) {
|
|
584
|
+
logger.debug("Failed to verify; the document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin.", {
|
|
585
|
+
keyId,
|
|
586
|
+
documentUrl: documentUrl.href,
|
|
587
|
+
actorId: object.id?.href
|
|
588
|
+
});
|
|
589
|
+
await keyCache?.set(cacheKey, null);
|
|
590
|
+
return {
|
|
591
|
+
key: null,
|
|
592
|
+
cached: false
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
ownerDocument = object;
|
|
596
|
+
const keys = cls === CryptographicKey ? object.getPublicKeys({
|
|
597
|
+
documentLoader,
|
|
598
|
+
contextLoader,
|
|
599
|
+
suppressError: true,
|
|
600
|
+
tracerProvider
|
|
601
|
+
}) : object.getAssertionMethods({
|
|
602
|
+
documentLoader,
|
|
603
|
+
contextLoader,
|
|
604
|
+
suppressError: true,
|
|
605
|
+
tracerProvider
|
|
606
|
+
});
|
|
607
|
+
let length = 0;
|
|
608
|
+
let lastKey = null;
|
|
609
|
+
try {
|
|
610
|
+
for await (const k of keys) {
|
|
611
|
+
length++;
|
|
612
|
+
lastKey = k;
|
|
613
|
+
if (k.id?.href === keyId) {
|
|
614
|
+
key = k;
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
} catch (e) {
|
|
619
|
+
if (!(e instanceof TypeError)) throw e;
|
|
620
|
+
logger.debug("Failed to verify; a malformed key was encountered while iterating the keys of {keyId}; treating it as a missing key: {error}", {
|
|
621
|
+
keyId,
|
|
622
|
+
error: e
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
const keyIdUrl = new URL(keyId);
|
|
626
|
+
if (key == null && keyIdUrl.hash === "" && length === 1) key = lastKey;
|
|
627
|
+
if (key == null) {
|
|
628
|
+
logger.debug("Failed to verify; object {keyId} returned an {actorType}, but has no key matching {keyId}.", {
|
|
629
|
+
keyId,
|
|
630
|
+
actorType: object.constructor.name
|
|
631
|
+
});
|
|
632
|
+
await keyCache?.set(cacheKey, null);
|
|
633
|
+
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
634
|
+
return {
|
|
635
|
+
key: null,
|
|
636
|
+
cached: false
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
} else {
|
|
640
|
+
logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
|
|
641
|
+
await keyCache?.set(cacheKey, null);
|
|
642
|
+
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
643
|
+
return {
|
|
644
|
+
key: null,
|
|
645
|
+
cached: false
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
if (key.publicKey == null) {
|
|
649
|
+
logger.debug("Failed to verify; key {keyId} has no publicKeyPem field.", { keyId });
|
|
650
|
+
await keyCache?.set(cacheKey, null);
|
|
651
|
+
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
652
|
+
return {
|
|
653
|
+
key: null,
|
|
654
|
+
cached: false
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
658
|
+
if (!(keyIdBoundByCaller && claimedOwnerId != null && claimedOwnerId.href === stripFragment(cacheKey)?.href)) {
|
|
659
|
+
if (claimedOwnerId != null && isPortableId(claimedOwnerId)) {
|
|
660
|
+
logger.debug("Failed to verify; key {keyId} claims the portable actor {claimedOwnerId} as its owner, but is not its gateway key.", {
|
|
661
|
+
keyId,
|
|
662
|
+
claimedOwnerId: claimedOwnerId.href
|
|
663
|
+
});
|
|
664
|
+
await keyCache?.set(cacheKey, null);
|
|
665
|
+
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
666
|
+
return {
|
|
667
|
+
key: null,
|
|
668
|
+
cached: false
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
|
|
672
|
+
else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
|
|
673
|
+
if (await verifyKeyOwnership(key, {
|
|
674
|
+
documentLoader,
|
|
675
|
+
contextLoader,
|
|
676
|
+
tracerProvider
|
|
677
|
+
}) == null) {
|
|
678
|
+
logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
|
|
679
|
+
keyId,
|
|
680
|
+
claimedOwnerId: claimedOwnerId.href
|
|
681
|
+
});
|
|
682
|
+
await keyCache?.set(cacheKey, null);
|
|
683
|
+
return {
|
|
684
|
+
key: null,
|
|
685
|
+
cached: false
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (keyCache != null) {
|
|
691
|
+
await keyCache.set(cacheKey, key);
|
|
692
|
+
logger.debug("Key {keyId} cached.", { keyId });
|
|
693
|
+
}
|
|
694
|
+
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
695
|
+
return {
|
|
696
|
+
key,
|
|
697
|
+
cached: false
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Tells whether a key claims a portable actor as its owner. Such a key can
|
|
702
|
+
* only be a gateway key, which is never cached under its ID, so a cached one
|
|
703
|
+
* was cached by an older version that trusted web origins, and is fetched
|
|
704
|
+
* again.
|
|
705
|
+
*/
|
|
706
|
+
function claimsPortableOwner(key) {
|
|
707
|
+
const ownerId = key instanceof CryptographicKey ? key.ownerId : key?.controllerId;
|
|
708
|
+
return ownerId != null && isPortableId(ownerId);
|
|
709
|
+
}
|
|
710
|
+
async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, onFetchError) {
|
|
711
|
+
const start = performance.now();
|
|
712
|
+
let outcome = { result: "error" };
|
|
713
|
+
try {
|
|
714
|
+
const logger = getLogger([
|
|
715
|
+
"fedify",
|
|
716
|
+
"sig",
|
|
717
|
+
"key"
|
|
718
|
+
]);
|
|
719
|
+
const keyId = cacheKey.href;
|
|
720
|
+
const keyCache = options.keyCache;
|
|
721
|
+
if (isPortableUri(cacheKey)) {
|
|
722
|
+
const result = await fetchPortableActorKey(cacheKey, cls, options, onCachedUnavailable, onFetchError, logger);
|
|
723
|
+
outcome = result.outcome;
|
|
724
|
+
return result.result;
|
|
725
|
+
}
|
|
726
|
+
if (isCompatibleKeyId(cacheKey)) {
|
|
727
|
+
const result = await fetchCompatibleKey(cacheKey, cls, options, onCachedUnavailable, onFetchError, logger);
|
|
728
|
+
outcome = result.outcome;
|
|
729
|
+
return result.result;
|
|
730
|
+
}
|
|
731
|
+
const didKey = await resolveDidKey(cacheKey, cls, keyCache, logger);
|
|
732
|
+
if (didKey != null) {
|
|
733
|
+
outcome = { result: didKey.key == null ? "invalid" : didKey.cached ? "hit" : "fetched" };
|
|
734
|
+
return didKey;
|
|
735
|
+
}
|
|
736
|
+
const cached = await getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger);
|
|
737
|
+
if (cached?.key === null && cached.cached) {
|
|
738
|
+
const cachedUnavailable = await onCachedUnavailable(cacheKey, keyId, keyCache, logger);
|
|
739
|
+
outcome = { result: "hit" };
|
|
740
|
+
return cachedUnavailable;
|
|
741
|
+
}
|
|
742
|
+
if (cached != null && !claimsPortableOwner(cached.key)) {
|
|
743
|
+
outcome = { result: "hit" };
|
|
744
|
+
return cached;
|
|
745
|
+
}
|
|
746
|
+
logger.debug("Fetching key {keyId} to verify signature...", { keyId });
|
|
747
|
+
let document;
|
|
748
|
+
let documentUrl = cacheKey;
|
|
749
|
+
try {
|
|
750
|
+
const remoteDocument = await (options.documentLoader ?? getDocumentLoader())(keyId);
|
|
751
|
+
document = remoteDocument.document;
|
|
752
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", cacheKey);
|
|
753
|
+
} catch (error) {
|
|
754
|
+
const classified = classifyFetchError(error);
|
|
755
|
+
const errored = await onFetchError(error, cacheKey, keyId, keyCache, logger);
|
|
756
|
+
outcome = classified;
|
|
757
|
+
return errored;
|
|
758
|
+
}
|
|
759
|
+
const resolved = await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
|
|
760
|
+
outcome = { result: resolved.key != null ? "fetched" : "invalid" };
|
|
761
|
+
return resolved;
|
|
762
|
+
} finally {
|
|
763
|
+
recordKeyLookup(options.meterProvider, {
|
|
764
|
+
durationMs: getDurationMs(start),
|
|
765
|
+
result: outcome.result,
|
|
766
|
+
remoteUrl: cacheKey,
|
|
767
|
+
cacheEnabled: options.keyCache != null,
|
|
768
|
+
statusCode: outcome.statusCode
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Tells which purpose a lookup of a key at a compatible identifier serves.
|
|
774
|
+
*/
|
|
775
|
+
function getCompatibleKeyScope(cls, options) {
|
|
776
|
+
if (cls !== CryptographicKey) return "multikey";
|
|
777
|
+
return options.portableKeyResolvers == null ? "cryptographicKey" : "httpSignature";
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Looks up a key at an [FEP-ef61] compatible identifier. Such a key may be
|
|
781
|
+
* a gateway key of a portable actor, which is accepted for some purposes but
|
|
782
|
+
* not for others, so everything resolved from the document at the key ID is
|
|
783
|
+
* cached in a namespace of the lookup's purpose, and one purpose never reuses
|
|
784
|
+
* what another resolved, nor what another rejected. A failure to fetch
|
|
785
|
+
* the document fails every purpose alike, so it is cached in the shared
|
|
786
|
+
* namespace, as for any other key.
|
|
787
|
+
*
|
|
788
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
789
|
+
*/
|
|
790
|
+
async function fetchCompatibleKey(cacheKey, cls, options, onCachedUnavailable, onFetchError, logger) {
|
|
791
|
+
const keyId = cacheKey.href;
|
|
792
|
+
const sharedCache = options.keyCache;
|
|
793
|
+
const scopedCache = sharedCache?.compatibleKeyScope?.(getCompatibleKeyScope(cls, options));
|
|
794
|
+
const cached = await getCachedCompatibleKey(cacheKey, keyId, cls, scopedCache, logger);
|
|
795
|
+
if (cached != null) return {
|
|
796
|
+
result: cached,
|
|
797
|
+
outcome: { result: "hit" }
|
|
798
|
+
};
|
|
799
|
+
if (await sharedCache?.get(cacheKey) === null) {
|
|
800
|
+
logger.debug("Entry {keyId} found in cache, but it could not be fetched.", { keyId });
|
|
801
|
+
return {
|
|
802
|
+
result: await onCachedUnavailable(cacheKey, keyId, sharedCache, logger),
|
|
803
|
+
outcome: { result: "hit" }
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
logger.debug("Fetching key {keyId} to verify signature...", { keyId });
|
|
807
|
+
let document;
|
|
808
|
+
let documentUrl;
|
|
809
|
+
try {
|
|
810
|
+
const remoteDocument = await (options.documentLoader ?? getDocumentLoader())(keyId);
|
|
811
|
+
document = remoteDocument.document;
|
|
812
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", cacheKey);
|
|
813
|
+
} catch (error) {
|
|
814
|
+
const outcome = classifyFetchError(error);
|
|
815
|
+
const result = await onFetchError(error, cacheKey, keyId, sharedCache, logger);
|
|
816
|
+
await scopedCache?.delete(cacheKey);
|
|
817
|
+
return {
|
|
818
|
+
result,
|
|
819
|
+
outcome
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
const resolved = await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
|
|
823
|
+
...options,
|
|
824
|
+
keyCache: scopedCache
|
|
825
|
+
}, logger);
|
|
826
|
+
return {
|
|
827
|
+
result: resolved,
|
|
828
|
+
outcome: { result: resolved.key != null ? "fetched" : "invalid" }
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Looks up a key of an [FEP-ef61] portable actor at an `ap:` or `ap+ef61:`
|
|
833
|
+
* key ID. Such a key is accepted only for HTTP Signatures, and is rejected
|
|
834
|
+
* for any other purpose without being fetched. Like keys at compatible
|
|
835
|
+
* identifiers, what it resolves to is cached in the namespace of the lookup's
|
|
836
|
+
* purpose, and a failure to fetch the actor's document from every gateway is
|
|
837
|
+
* cached in the shared namespace. A failure that is neither, e.g., one
|
|
838
|
+
* gateway being unreachable while another served an invalid document, is not
|
|
839
|
+
* cached at all.
|
|
840
|
+
*
|
|
841
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
842
|
+
*/
|
|
843
|
+
async function fetchPortableActorKey(cacheKey, cls, options, onCachedUnavailable, onFetchError, logger) {
|
|
844
|
+
const keyId = cacheKey.href;
|
|
845
|
+
const resolvers = options.portableKeyResolvers;
|
|
846
|
+
if (resolvers == null || cls !== CryptographicKey) {
|
|
847
|
+
logger.debug("Failed to verify; key {keyId} is a key of a portable actor, which is only accepted for HTTP Signatures.", { keyId });
|
|
848
|
+
return {
|
|
849
|
+
result: {
|
|
850
|
+
key: null,
|
|
851
|
+
cached: false
|
|
852
|
+
},
|
|
853
|
+
outcome: { result: "invalid" }
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
const sharedCache = options.keyCache;
|
|
857
|
+
const scopedCache = sharedCache?.compatibleKeyScope?.("httpSignature");
|
|
858
|
+
const cached = await getCachedCompatibleKey(cacheKey, keyId, cls, scopedCache, logger);
|
|
859
|
+
if (cached != null) return {
|
|
860
|
+
result: cached,
|
|
861
|
+
outcome: { result: "hit" }
|
|
862
|
+
};
|
|
863
|
+
if (await sharedCache?.get(cacheKey) === null) {
|
|
864
|
+
logger.debug("Entry {keyId} found in cache, but it could not be fetched.", { keyId });
|
|
865
|
+
return {
|
|
866
|
+
result: await onCachedUnavailable(cacheKey, keyId, sharedCache, logger),
|
|
867
|
+
outcome: { result: "hit" }
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
logger.debug("Resolving key {keyId} to verify signature...", { keyId });
|
|
871
|
+
const resolution = await resolvers.actorKey(cacheKey);
|
|
872
|
+
if (resolution.type === "verified") {
|
|
873
|
+
attachResolvedKeyOwnerEvidence(resolution, scopedCache, await scopedCache?.set(cacheKey, resolution.key, {
|
|
874
|
+
expires: resolution.expires,
|
|
875
|
+
owner: resolution.document
|
|
876
|
+
}));
|
|
877
|
+
return {
|
|
878
|
+
result: {
|
|
879
|
+
key: resolution.key,
|
|
880
|
+
cached: false
|
|
881
|
+
},
|
|
882
|
+
outcome: { result: "fetched" }
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
if (resolution.type === "rejected") {
|
|
886
|
+
await scopedCache?.set(cacheKey, null);
|
|
887
|
+
return {
|
|
888
|
+
result: {
|
|
889
|
+
key: null,
|
|
890
|
+
cached: false
|
|
891
|
+
},
|
|
892
|
+
outcome: { result: "invalid" }
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
await scopedCache?.delete(cacheKey);
|
|
896
|
+
return {
|
|
897
|
+
result: await onFetchError(resolution.error, cacheKey, keyId, resolution.cacheable ? sharedCache : void 0, logger),
|
|
898
|
+
outcome: resolution.cacheable ? classifyFetchError(resolution.error) : { result: "error" }
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
902
|
+
return await fetchKeyWithResult(typeof keyId === "string" ? parseKeyIdOrThrow(keyId) : keyId, cls, options, async (_cacheKey, _keyId, _keyCache, _logger) => {
|
|
903
|
+
const check = options[verificationObservation]?.check;
|
|
904
|
+
if (check != null) try {
|
|
905
|
+
const result = await _keyCache?.getFetchError?.(_cacheKey);
|
|
906
|
+
if (result != null) check.reason = {
|
|
907
|
+
type: "keyFetchError",
|
|
908
|
+
keyId: new URL(_cacheKey.href),
|
|
909
|
+
result
|
|
910
|
+
};
|
|
911
|
+
} catch {}
|
|
912
|
+
return {
|
|
913
|
+
key: null,
|
|
914
|
+
cached: true
|
|
915
|
+
};
|
|
916
|
+
}, async (error, cacheKey, keyId, keyCache, logger) => {
|
|
917
|
+
const check = options[verificationObservation]?.check;
|
|
918
|
+
if (check != null) {
|
|
919
|
+
let result;
|
|
920
|
+
try {
|
|
921
|
+
result = error instanceof FetchError && error.response != null ? {
|
|
922
|
+
status: error.response.status,
|
|
923
|
+
response: error.response.clone()
|
|
924
|
+
} : { error: error instanceof Error ? error : new Error(String(error)) };
|
|
925
|
+
} catch {
|
|
926
|
+
result = { error: error instanceof Error ? error : /* @__PURE__ */ new Error() };
|
|
927
|
+
}
|
|
928
|
+
check.reason = {
|
|
929
|
+
type: "keyFetchError",
|
|
930
|
+
keyId: new URL(cacheKey.href),
|
|
931
|
+
result
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
logger.debug("Failed to fetch key {keyId}.", {
|
|
935
|
+
keyId,
|
|
936
|
+
error
|
|
937
|
+
});
|
|
938
|
+
await keyCache?.set(cacheKey, null);
|
|
939
|
+
if (error instanceof FetchError && error.response != null) await keyCache?.setFetchError?.(cacheKey, {
|
|
940
|
+
status: error.response.status,
|
|
941
|
+
response: error.response.clone()
|
|
942
|
+
});
|
|
943
|
+
else await keyCache?.setFetchError?.(cacheKey, { error: error instanceof Error ? error : new Error(String(error)) });
|
|
944
|
+
return {
|
|
945
|
+
key: null,
|
|
946
|
+
cached: false
|
|
947
|
+
};
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
//#endregion
|
|
951
|
+
export { fetchKeyDetailed as a, validateCryptoKey as c, fetchKey as i, verifyKeyOwnership as l, exportJwk as n, generateCryptoKeyPair as o, fetchActorDocument as r, importJwk as s, bypassKeyCacheReads as t };
|