@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,1299 @@
|
|
|
1
|
+
import { Temporal } from "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { i as getPortableDid, l as isPortableId } from "./portable-key-id-Ca30-hOa.mjs";
|
|
5
|
+
import { a as verificationObservation, i as triedKey, n as observeCheck, t as observeAttempt } from "./verification-Dpfcw8ml.mjs";
|
|
6
|
+
import { n as version, t as name } from "./deno-oR5aJKpU.mjs";
|
|
7
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-CYg9yxBL.mjs";
|
|
8
|
+
import { c as validateCryptoKey, i as fetchKey, t as bypassKeyCacheReads } from "./key-BspPiXqn.mjs";
|
|
9
|
+
import { n as preloadedOnlyDocumentLoader } from "./public-audience--klBIOk1.mjs";
|
|
10
|
+
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-4IJqPYos.mjs";
|
|
11
|
+
import { c as getNormalizationContextLoader } from "./ld-CzaP-hJl.mjs";
|
|
12
|
+
import { getLogger } from "@logtape/logtape";
|
|
13
|
+
import { Activity, DataIntegrityProof, Multikey, getTypeId } from "@fedify/vocab";
|
|
14
|
+
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
15
|
+
import { encodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, parseGatewayUrl, parseIri } from "@fedify/vocab-runtime";
|
|
16
|
+
import { encodeHex } from "byte-encodings/hex";
|
|
17
|
+
import { getPortableActorGateways } from "@fedify/vocab-runtime/internal/jsonld-cache";
|
|
18
|
+
import { isPlainJsonTree, retainSignedRepresentation } from "@fedify/vocab-runtime/internal/signed-representation";
|
|
19
|
+
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
20
|
+
import serialize from "json-canon";
|
|
21
|
+
//#region src/sig/proof.ts
|
|
22
|
+
/**
|
|
23
|
+
* Known Object Integrity Proof `cryptosuite` values, used to keep
|
|
24
|
+
* `object_integrity_proofs.cryptosuite` on a bounded set of spec-defined
|
|
25
|
+
* string values. Fedify currently signs and verifies only
|
|
26
|
+
* `eddsa-jcs-2022`; other values come in only from external proofs and are
|
|
27
|
+
* dropped from the metric attribute to avoid attacker-controlled
|
|
28
|
+
* cardinality.
|
|
29
|
+
*/
|
|
30
|
+
const OIP_KNOWN_CRYPTOSUITES = /* @__PURE__ */ new Set(["eddsa-jcs-2022"]);
|
|
31
|
+
const logger = getLogger([
|
|
32
|
+
"fedify",
|
|
33
|
+
"sig",
|
|
34
|
+
"proof"
|
|
35
|
+
]);
|
|
36
|
+
const SECURITY_NAMESPACE = "https://w3id.org/security#";
|
|
37
|
+
const SECURITY_PROOF = `${SECURITY_NAMESPACE}proof`;
|
|
38
|
+
const SECURITY_PROOF_VALUE = `${SECURITY_NAMESPACE}proofValue`;
|
|
39
|
+
const DATA_INTEGRITY_PROOF = `${SECURITY_NAMESPACE}DataIntegrityProof`;
|
|
40
|
+
const SECURITY_VERIFICATION_METHOD = `${SECURITY_NAMESPACE}verificationMethod`;
|
|
41
|
+
const SECURITY_EXPIRATION = `${SECURITY_NAMESPACE}expiration`;
|
|
42
|
+
const SECURITY_DOMAIN = `${SECURITY_NAMESPACE}domain`;
|
|
43
|
+
const SECURITY_CHALLENGE = `${SECURITY_NAMESPACE}challenge`;
|
|
44
|
+
const SECURITY_NONCE = `${SECURITY_NAMESPACE}nonce`;
|
|
45
|
+
const SECURITY_PREVIOUS_PROOF = `${SECURITY_NAMESPACE}previousProof`;
|
|
46
|
+
/**
|
|
47
|
+
* Checks if the given JSON-LD document has a DataIntegrityProof-like object,
|
|
48
|
+
* without fully deserializing it into vocabulary classes.
|
|
49
|
+
* @param jsonLd The JSON-LD document to check.
|
|
50
|
+
* @returns `true` if the document has a proof-like object; `false` otherwise.
|
|
51
|
+
* @since 2.2.0
|
|
52
|
+
*/
|
|
53
|
+
function hasProofLike(jsonLd) {
|
|
54
|
+
if (typeof jsonLd !== "object" || jsonLd == null) return false;
|
|
55
|
+
const record = jsonLd;
|
|
56
|
+
const proof = record.proof ?? record["https://w3id.org/security#proof"];
|
|
57
|
+
const getField = (source, compact, expanded) => source[compact] ?? source[expanded];
|
|
58
|
+
const isReference = (value) => {
|
|
59
|
+
if (typeof value === "string") return true;
|
|
60
|
+
if (Array.isArray(value)) return value.some(isReference);
|
|
61
|
+
return typeof value === "object" && value != null && ("id" in value && typeof value.id === "string" || "@id" in value && typeof value["@id"] === "string" || "@value" in value && typeof value["@value"] === "string");
|
|
62
|
+
};
|
|
63
|
+
const hasType = (value) => {
|
|
64
|
+
if (typeof value === "string") return value === "DataIntegrityProof" || value === "https://w3id.org/security#DataIntegrityProof";
|
|
65
|
+
if (Array.isArray(value)) return value.some(hasType);
|
|
66
|
+
return false;
|
|
67
|
+
};
|
|
68
|
+
const isProofLike = (value) => {
|
|
69
|
+
if (typeof value !== "object" || value == null) return false;
|
|
70
|
+
const proofRecord = value;
|
|
71
|
+
return hasType(proofRecord.type ?? proofRecord["@type"]) && isReference(getField(proofRecord, "verificationMethod", "https://w3id.org/security#verificationMethod")) && isReference(getField(proofRecord, "proofPurpose", "https://w3id.org/security#proofPurpose")) && isReference(getField(proofRecord, "proofValue", "https://w3id.org/security#proofValue"));
|
|
72
|
+
};
|
|
73
|
+
return Array.isArray(proof) ? proof.some(isProofLike) : isProofLike(proof);
|
|
74
|
+
}
|
|
75
|
+
function isJsonMap(value) {
|
|
76
|
+
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Assembles the secured JSON document from the bytes that were just signed.
|
|
80
|
+
*
|
|
81
|
+
* The document is not re-derived from the vocabulary object: it is
|
|
82
|
+
* `compactMsg`, the value whose JCS form was hashed, with the serialized
|
|
83
|
+
* proof added. Every part of it is then checked against the digests and the
|
|
84
|
+
* signature that {@link createProofInternal} produced, so a document that is
|
|
85
|
+
* returned verifies under the map-local compound-proof profile by
|
|
86
|
+
* construction. Anything that does not check out yields `null`, and the
|
|
87
|
+
* caller simply does not retain a representation.
|
|
88
|
+
*/
|
|
89
|
+
async function captureSecuredDocument(proof, compactMsg, msgCanon, proofCanon, proofValue, contextLoader) {
|
|
90
|
+
if (!isJsonMap(compactMsg)) return null;
|
|
91
|
+
const documentContext = compactMsg["@context"];
|
|
92
|
+
if (documentContext == null) return null;
|
|
93
|
+
if (globalThis.Object.hasOwn(compactMsg, "proof")) return null;
|
|
94
|
+
try {
|
|
95
|
+
const proofJson = await proof.toJsonLd({
|
|
96
|
+
format: "compact",
|
|
97
|
+
contextLoader,
|
|
98
|
+
context: documentContext
|
|
99
|
+
});
|
|
100
|
+
if (!isJsonMap(proofJson)) return null;
|
|
101
|
+
const { proofValue: serializedProofValue, ...proofConfiguration } = proofJson;
|
|
102
|
+
if (serializedProofValue !== proofValue) return null;
|
|
103
|
+
if (serialize(proofConfiguration) !== proofCanon) return null;
|
|
104
|
+
const securedDocument = {
|
|
105
|
+
...structuredClone(compactMsg),
|
|
106
|
+
proof: structuredClone(proofJson)
|
|
107
|
+
};
|
|
108
|
+
const { proof: _embeddedProof, ...unsecuredDocument } = securedDocument;
|
|
109
|
+
if (serialize(unsecuredDocument) !== msgCanon) return null;
|
|
110
|
+
if (!isPlainJsonTree(securedDocument)) return null;
|
|
111
|
+
return securedDocument;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
logger.debug("Failed to capture the secured JSON document for a created proof; the signed object will not preserve its representation when it is embedded in another object.\n{error}", { error });
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function createProofInternal(object, privateKey, keyId, { contextLoader, context, created } = {}, capture = false) {
|
|
118
|
+
validateCryptoKey(privateKey, "private");
|
|
119
|
+
if (privateKey.algorithm.name !== "Ed25519") throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
|
|
120
|
+
let compactMsg = await object.clone({ proofs: [] }).toJsonLd({
|
|
121
|
+
format: "compact",
|
|
122
|
+
contextLoader,
|
|
123
|
+
context
|
|
124
|
+
});
|
|
125
|
+
compactMsg = await normalizeOutgoingActivityJsonLd(compactMsg, contextLoader, { preserveNestedSecuredDocuments: true });
|
|
126
|
+
const msgCanon = serialize(compactMsg);
|
|
127
|
+
const encoder = new TextEncoder();
|
|
128
|
+
const msgBytes = encoder.encode(msgCanon);
|
|
129
|
+
const msgDigest = await crypto.subtle.digest("SHA-256", msgBytes);
|
|
130
|
+
created ??= Temporal.Now.instant();
|
|
131
|
+
const proofCanon = serialize({
|
|
132
|
+
"@context": compactMsg["@context"],
|
|
133
|
+
type: "DataIntegrityProof",
|
|
134
|
+
cryptosuite: "eddsa-jcs-2022",
|
|
135
|
+
verificationMethod: keyId.href,
|
|
136
|
+
proofPurpose: "assertionMethod",
|
|
137
|
+
created: created.toString()
|
|
138
|
+
});
|
|
139
|
+
const proofBytes = encoder.encode(proofCanon);
|
|
140
|
+
const proofDigest = await crypto.subtle.digest("SHA-256", proofBytes);
|
|
141
|
+
const digest = new Uint8Array(proofDigest.byteLength + msgDigest.byteLength);
|
|
142
|
+
digest.set(new Uint8Array(proofDigest), 0);
|
|
143
|
+
digest.set(new Uint8Array(msgDigest), proofDigest.byteLength);
|
|
144
|
+
const sig = new Uint8Array(await crypto.subtle.sign("Ed25519", privateKey, digest));
|
|
145
|
+
const proof = new DataIntegrityProof({
|
|
146
|
+
cryptosuite: "eddsa-jcs-2022",
|
|
147
|
+
verificationMethod: keyId,
|
|
148
|
+
proofPurpose: "assertionMethod",
|
|
149
|
+
created,
|
|
150
|
+
proofValue: sig
|
|
151
|
+
});
|
|
152
|
+
return {
|
|
153
|
+
proof,
|
|
154
|
+
securedDocument: capture ? await captureSecuredDocument(proof, compactMsg, msgCanon, proofCanon, new TextDecoder().decode(encodeMultibase("base58btc", sig)), contextLoader) : null
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Creates a proof for the given object.
|
|
159
|
+
* @param object The object to create a proof for.
|
|
160
|
+
* @param privateKey The private key to sign the proof with.
|
|
161
|
+
* @param keyId The key ID to use in the proof. It will be used by the verifier.
|
|
162
|
+
* @param options Additional options. See also {@link CreateProofOptions}.
|
|
163
|
+
* @returns The created proof.
|
|
164
|
+
* @throws {TypeError} If the private key is invalid or unsupported.
|
|
165
|
+
* @since 0.10.0
|
|
166
|
+
*/
|
|
167
|
+
async function createProof(object, privateKey, keyId, options = {}) {
|
|
168
|
+
const { proof } = await createProofInternal(object, privateKey, keyId, options);
|
|
169
|
+
return proof;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Signs the given object with the private key and returns the signed object.
|
|
173
|
+
* @param object The object to create a proof for.
|
|
174
|
+
* @param privateKey The private key to sign the proof with.
|
|
175
|
+
* @param keyId The key ID to use in the proof. It will be used by the verifier.
|
|
176
|
+
* @param options Additional options. See also {@link SignObjectOptions}.
|
|
177
|
+
* @returns The signed object.
|
|
178
|
+
* @throws {TypeError} If the private key is invalid or unsupported.
|
|
179
|
+
* @since 0.10.0
|
|
180
|
+
*/
|
|
181
|
+
async function signObject(object, privateKey, keyId, options = {}) {
|
|
182
|
+
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.sign", { attributes: { "activitypub.object.type": getTypeId(object).href } }, async (span) => {
|
|
183
|
+
try {
|
|
184
|
+
if (object.id != null) span.setAttribute("activitypub.object.id", object.id.href);
|
|
185
|
+
const existingProofs = [];
|
|
186
|
+
for await (const proof of object.getProofs(options)) existingProofs.push(proof);
|
|
187
|
+
const { proof, securedDocument } = await createProofInternal(object, privateKey, keyId, options, existingProofs.length < 1);
|
|
188
|
+
if (span.isRecording()) {
|
|
189
|
+
if (proof.cryptosuite != null) span.setAttribute("object_integrity_proofs.cryptosuite", proof.cryptosuite);
|
|
190
|
+
if (proof.verificationMethodId != null) span.setAttribute("object_integrity_proofs.key_id", proof.verificationMethodId.href);
|
|
191
|
+
if (proof.proofValue != null) span.setAttribute("object_integrity_proofs.signature", encodeHex(proof.proofValue));
|
|
192
|
+
}
|
|
193
|
+
const signed = object.clone({ proofs: [...existingProofs, proof] });
|
|
194
|
+
if (securedDocument != null) retainSignedRepresentation(signed, securedDocument);
|
|
195
|
+
else if (existingProofs.length > 0) logger.debug("The object {objectId} already had {proofCount} proof(s), so its signed representation is not retained for embedding; the map-local compound-proof profile accepts exactly one direct proof per map.", {
|
|
196
|
+
objectId: object.id?.href,
|
|
197
|
+
proofCount: existingProofs.length
|
|
198
|
+
});
|
|
199
|
+
return signed;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
span.setStatus({
|
|
202
|
+
code: SpanStatusCode.ERROR,
|
|
203
|
+
message: String(error)
|
|
204
|
+
});
|
|
205
|
+
throw error;
|
|
206
|
+
} finally {
|
|
207
|
+
span.end();
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Verifies the given proof for the object.
|
|
213
|
+
* @param jsonLd The JSON-LD object to verify the proof for. Its proof
|
|
214
|
+
* properties are excluded from the message, but matching proof
|
|
215
|
+
* occurrences are used to authenticate the received proof
|
|
216
|
+
* configuration.
|
|
217
|
+
* @param proof The proof to verify.
|
|
218
|
+
* @param options Additional options. See also {@link VerifyProofOptions}.
|
|
219
|
+
* @returns The public key that was used to sign the proof, or `null` if the
|
|
220
|
+
* proof is invalid.
|
|
221
|
+
* @since 0.10.0
|
|
222
|
+
* @since 2.4.0 Matching proof configurations are authenticated.
|
|
223
|
+
*/
|
|
224
|
+
async function verifyProof(jsonLd, proof, options = {}) {
|
|
225
|
+
return await verifyProofWithMessageDigestCache(jsonLd, proof, options);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Verifies the one direct literal proof on a map-local secured document.
|
|
229
|
+
*
|
|
230
|
+
* Unlike {@link verifyProof}, this removes only the literal `proof` member
|
|
231
|
+
* from the message digest. JSON-LD aliases remain part of the signed input,
|
|
232
|
+
* and the document's received `@context` is not replaced by the proof
|
|
233
|
+
* configuration context.
|
|
234
|
+
*
|
|
235
|
+
* @internal
|
|
236
|
+
*/
|
|
237
|
+
async function verifyMapLocalProof(jsonLd, options = {}) {
|
|
238
|
+
if (!isJsonLdNode(jsonLd) || !globalThis.Object.hasOwn(jsonLd, "proof") || !isJsonLdNode(jsonLd.proof)) return null;
|
|
239
|
+
const rawProof = jsonLd.proof;
|
|
240
|
+
const previousChecks = options[verificationObservation]?.attempt?.checks.length;
|
|
241
|
+
const malformed = () => observeCheck(options, {
|
|
242
|
+
mechanism: "objectIntegrity",
|
|
243
|
+
proofId: typeof rawProof.id === "string" ? rawProof.id : typeof rawProof["@id"] === "string" ? rawProof["@id"] : null,
|
|
244
|
+
proofIndex: previousChecks ?? null
|
|
245
|
+
}, getDeclaredProofKeyId(rawProof), () => Promise.resolve(null));
|
|
246
|
+
const proofContextLoader = getNormalizationContextLoader(preloadedOnlyDocumentLoader);
|
|
247
|
+
try {
|
|
248
|
+
const [candidate] = await parseRawProofCandidates(jsonLd, [jsonLd.proof], options, proofContextLoader);
|
|
249
|
+
if (candidate.proof == null) return await malformed();
|
|
250
|
+
return await verifyProofWithMessageDigestCache(jsonLd, candidate.proof, options, {
|
|
251
|
+
proofContextLoader,
|
|
252
|
+
proofPropertyMode: "literal"
|
|
253
|
+
}, candidate);
|
|
254
|
+
} catch {
|
|
255
|
+
if (previousChecks != null && options[verificationObservation]?.attempt?.checks.length === previousChecks) return await malformed();
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function getDeclaredProofKeyId(value, propertyNames = ["verificationMethod", "https://w3id.org/security#verificationMethod"]) {
|
|
260
|
+
if (!isJsonLdNode(value)) return null;
|
|
261
|
+
const method = Array.from(propertyNames, (name) => value[name]).find((v) => v != null);
|
|
262
|
+
const candidate = Array.isArray(method) ? method[0] : method;
|
|
263
|
+
if (typeof candidate === "string") return candidate;
|
|
264
|
+
return isJsonLdNode(candidate) && typeof candidate["@id"] === "string" ? candidate["@id"] : null;
|
|
265
|
+
}
|
|
266
|
+
async function verifyProofWithMessageDigestCache(jsonLd, proof, options, messageDigestCache = {}, rawProofCandidate, keyIdBoundByCaller = false) {
|
|
267
|
+
if (options[verificationObservation] == null) return await verifyProofWithMessageDigestCache(jsonLd, proof, {
|
|
268
|
+
...options,
|
|
269
|
+
[verificationObservation]: {
|
|
270
|
+
attempts: [],
|
|
271
|
+
attempt: { checks: [] },
|
|
272
|
+
captureRawKeyIds: false
|
|
273
|
+
}
|
|
274
|
+
}, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
275
|
+
if (options[verificationObservation]?.attempt != null && options[verificationObservation]?.check == null) return await observeCheck(options, {
|
|
276
|
+
mechanism: "objectIntegrity",
|
|
277
|
+
proofId: proof.id?.href ?? null,
|
|
278
|
+
proofIndex: options[verificationObservation].attempt.checks.length
|
|
279
|
+
}, rawProofCandidate?.declaredKeyId ?? getDeclaredProofKeyId(rawProofCandidate?.value) ?? proof.verificationMethodId?.href ?? null, (observation) => verifyProofWithMessageDigestCache(jsonLd, proof, {
|
|
280
|
+
...options,
|
|
281
|
+
[verificationObservation]: observation
|
|
282
|
+
}, messageDigestCache, rawProofCandidate, keyIdBoundByCaller));
|
|
283
|
+
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
|
|
284
|
+
const start = performance.now();
|
|
285
|
+
let verified = false;
|
|
286
|
+
let threw = false;
|
|
287
|
+
const cryptosuite = proof.cryptosuite != null && OIP_KNOWN_CRYPTOSUITES.has(proof.cryptosuite) ? proof.cryptosuite : void 0;
|
|
288
|
+
if (span.isRecording()) {
|
|
289
|
+
if (proof.cryptosuite != null) span.setAttribute("object_integrity_proofs.cryptosuite", proof.cryptosuite);
|
|
290
|
+
if (proof.verificationMethodId != null) span.setAttribute("object_integrity_proofs.key_id", proof.verificationMethodId.href);
|
|
291
|
+
if (proof.proofValue != null) span.setAttribute("object_integrity_proofs.signature", encodeHex(proof.proofValue));
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
295
|
+
if (key == null) {
|
|
296
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
297
|
+
span.setAttribute("activitypub.verification.failure_reason", options[verificationObservation]?.check?.reason?.type ?? "invalidSignature");
|
|
298
|
+
} else verified = true;
|
|
299
|
+
return key;
|
|
300
|
+
} catch (error) {
|
|
301
|
+
threw = true;
|
|
302
|
+
span.setStatus({
|
|
303
|
+
code: SpanStatusCode.ERROR,
|
|
304
|
+
message: String(error)
|
|
305
|
+
});
|
|
306
|
+
throw error;
|
|
307
|
+
} finally {
|
|
308
|
+
const classified = threw ? "error" : verified ? "verified" : "rejected";
|
|
309
|
+
getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "object_integrity", classified, {
|
|
310
|
+
cryptosuite,
|
|
311
|
+
verificationFailureReason: verified || threw ? void 0 : options[verificationObservation]?.check?.reason?.type ?? "invalidSignature"
|
|
312
|
+
});
|
|
313
|
+
span.end();
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function expandContextPropertyIri(activeContext, key) {
|
|
318
|
+
const termId = jsonld.getContextValue(activeContext, key, "@id");
|
|
319
|
+
if (termId != null) return termId;
|
|
320
|
+
const colon = key.indexOf(":");
|
|
321
|
+
if (colon < 1) return key;
|
|
322
|
+
const prefix = key.substring(0, colon);
|
|
323
|
+
const suffix = key.substring(colon + 1);
|
|
324
|
+
if (prefix === "_" || suffix.startsWith("//")) return key;
|
|
325
|
+
const mapping = jsonld.getContextValue(activeContext, prefix);
|
|
326
|
+
if (typeof mapping === "object" && mapping != null && mapping._prefix === true && typeof mapping["@id"] === "string") return mapping["@id"] + suffix;
|
|
327
|
+
return key;
|
|
328
|
+
}
|
|
329
|
+
async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false, contextState) {
|
|
330
|
+
const names = new Set(defaults);
|
|
331
|
+
const context = jsonLd["@context"] ?? inheritedContext;
|
|
332
|
+
if (context == null && contextState?.initial == null) return names;
|
|
333
|
+
try {
|
|
334
|
+
const options = { documentLoader };
|
|
335
|
+
let activeContext = contextState?.initial ?? await jsonld.processContext(null, null, options);
|
|
336
|
+
if (contextState?.scoped != null) activeContext = await jsonld.processContext(activeContext, contextState.scoped, options);
|
|
337
|
+
if (context != null) activeContext = await jsonld.processContext(activeContext, context, options);
|
|
338
|
+
const typeScopedContext = activeContext;
|
|
339
|
+
for (const key of globalThis.Object.keys(jsonLd).sort()) {
|
|
340
|
+
if (key !== "@type" && expandContextPropertyIri(activeContext, key) !== "@type") continue;
|
|
341
|
+
const value = jsonLd[key];
|
|
342
|
+
const types = Array.isArray(value) ? value.slice().sort() : [value];
|
|
343
|
+
for (const type of types) {
|
|
344
|
+
if (typeof type !== "string") continue;
|
|
345
|
+
const scopedContext = jsonld.getContextValue(typeScopedContext, type, "@context");
|
|
346
|
+
if (scopedContext != null) activeContext = await jsonld.processContext(activeContext, scopedContext, options);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (contextState != null) contextState.active = activeContext;
|
|
350
|
+
for (const key of globalThis.Object.keys(jsonLd)) if (expandContextPropertyIri(activeContext, key) === propertyIri) names.add(key);
|
|
351
|
+
} catch {
|
|
352
|
+
if (rejectOnContextError) return null;
|
|
353
|
+
}
|
|
354
|
+
return names;
|
|
355
|
+
}
|
|
356
|
+
async function getProofPropertyNames(jsonLd, documentLoader = preloadedOnlyDocumentLoader) {
|
|
357
|
+
return await getJsonLdPropertyNames(jsonLd, SECURITY_PROOF, ["proof", SECURITY_PROOF], documentLoader) ?? /* @__PURE__ */ new Set(["proof", SECURITY_PROOF]);
|
|
358
|
+
}
|
|
359
|
+
async function createProofMessageDigests(jsonLd, proofContextLoader, context, proofPropertyMode = "jsonLd") {
|
|
360
|
+
const msg = { ...jsonLd };
|
|
361
|
+
if (proofPropertyMode === "literal") delete msg.proof;
|
|
362
|
+
else for (const property of await getProofPropertyNames(msg, proofContextLoader)) delete msg[property];
|
|
363
|
+
if (proofPropertyMode === "jsonLd" && context != null) msg["@context"] = structuredClone(context);
|
|
364
|
+
const encoder = new TextEncoder();
|
|
365
|
+
const digest = async (value) => {
|
|
366
|
+
const bytes = encoder.encode(serialize(value));
|
|
367
|
+
return await crypto.subtle.digest("SHA-256", bytes);
|
|
368
|
+
};
|
|
369
|
+
const onWire = await digest(msg);
|
|
370
|
+
let normalizedPromise;
|
|
371
|
+
return {
|
|
372
|
+
onWire,
|
|
373
|
+
normalized() {
|
|
374
|
+
normalizedPromise ??= (async () => {
|
|
375
|
+
const normalized = await normalizeOutgoingActivityJsonLd(msg, preloadedOnlyDocumentLoader);
|
|
376
|
+
return normalized === msg ? null : await digest(normalized);
|
|
377
|
+
})();
|
|
378
|
+
return normalizedPromise;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function contextValues(context) {
|
|
383
|
+
return Array.isArray(context) ? context : [context];
|
|
384
|
+
}
|
|
385
|
+
function equalJsonValues(left, right) {
|
|
386
|
+
try {
|
|
387
|
+
return serialize(left) === serialize(right);
|
|
388
|
+
} catch {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function contextStartsWith(documentContext, proofContext) {
|
|
393
|
+
if (documentContext == null) return false;
|
|
394
|
+
const documentValues = contextValues(documentContext);
|
|
395
|
+
const proofValues = contextValues(proofContext);
|
|
396
|
+
return proofValues.length <= documentValues.length && proofValues.every((value, index) => equalJsonValues(value, documentValues[index]));
|
|
397
|
+
}
|
|
398
|
+
function appendProofValues(values, value) {
|
|
399
|
+
if (Array.isArray(value)) for (const item of value) appendProofValues(values, item);
|
|
400
|
+
else if (isJsonLdNode(value) && Array.isArray(value["@graph"])) for (const item of value["@graph"]) appendProofValues(values, item);
|
|
401
|
+
else values.push(value);
|
|
402
|
+
}
|
|
403
|
+
async function getRawProofValues(jsonLd, documentLoader) {
|
|
404
|
+
const propertyNames = await getProofPropertyNames(jsonLd, documentLoader);
|
|
405
|
+
const values = [];
|
|
406
|
+
for (const [property, value] of globalThis.Object.entries(jsonLd)) if (propertyNames.has(property)) appendProofValues(values, value);
|
|
407
|
+
return values;
|
|
408
|
+
}
|
|
409
|
+
function sameBytes(left, right) {
|
|
410
|
+
if (left == null || right == null) return left === right;
|
|
411
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
412
|
+
}
|
|
413
|
+
function sameProof(left, right) {
|
|
414
|
+
return left.cryptosuite === right.cryptosuite && left.verificationMethodId?.href === right.verificationMethodId?.href && left.proofPurpose === right.proofPurpose && sameBytes(left.proofValue, right.proofValue) && left.created?.toString() === right.created?.toString();
|
|
415
|
+
}
|
|
416
|
+
function recordProofContexts(documentLoader) {
|
|
417
|
+
const documents = /* @__PURE__ */ new Map();
|
|
418
|
+
return {
|
|
419
|
+
loader: async (url, options) => {
|
|
420
|
+
const document = await documentLoader(url, options);
|
|
421
|
+
documents.set(url, document);
|
|
422
|
+
return document;
|
|
423
|
+
},
|
|
424
|
+
replay: (url, options) => {
|
|
425
|
+
const document = documents.get(url);
|
|
426
|
+
return document == null ? preloadedOnlyDocumentLoader(url, options) : Promise.resolve(document);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
async function getAliasedDeclaredProofKeyId(value, inheritedContext, contextLoader) {
|
|
431
|
+
if (!isJsonLdNode(value)) return null;
|
|
432
|
+
const contextState = {};
|
|
433
|
+
const names = await getJsonLdPropertyNames(value, "https://w3id.org/security#verificationMethod", ["verificationMethod", "https://w3id.org/security#verificationMethod"], contextLoader, inheritedContext, false, contextState);
|
|
434
|
+
const methodName = Array.from(names ?? []).find((name) => value[name] != null);
|
|
435
|
+
const method = methodName == null ? void 0 : value[methodName];
|
|
436
|
+
const candidate = Array.isArray(method) ? method[0] : method;
|
|
437
|
+
if (typeof candidate === "string") return candidate;
|
|
438
|
+
if (!isJsonLdNode(candidate)) return null;
|
|
439
|
+
const idNames = await getJsonLdPropertyNames(candidate, "@id", ["@id"], contextLoader, void 0, false, {
|
|
440
|
+
initial: contextState.active,
|
|
441
|
+
scoped: contextState.active == null || methodName == null ? void 0 : jsonld.getContextValue(contextState.active, methodName, "@context")
|
|
442
|
+
});
|
|
443
|
+
for (const name of idNames ?? []) if (typeof candidate[name] === "string") return candidate[name];
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
function normalizeDocumentUrl(url) {
|
|
447
|
+
try {
|
|
448
|
+
return formatIri(parseIri(url));
|
|
449
|
+
} catch {
|
|
450
|
+
return URL.canParse(url) ? new URL(url).href : url;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function getRawProofReference(value) {
|
|
454
|
+
const node = Array.isArray(value) && value.length === 1 ? value[0] : value;
|
|
455
|
+
if (typeof node === "string") return normalizeDocumentUrl(node);
|
|
456
|
+
if (!isJsonLdNode(node)) return void 0;
|
|
457
|
+
const id = node["@id"] ?? node.id;
|
|
458
|
+
return typeof id === "string" ? normalizeDocumentUrl(id) : void 0;
|
|
459
|
+
}
|
|
460
|
+
async function parseRawProofCandidates(jsonLd, values, options, documentLoader) {
|
|
461
|
+
const candidates = [];
|
|
462
|
+
for (const value of values) {
|
|
463
|
+
let parsed = null;
|
|
464
|
+
const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(documentLoader) : void 0;
|
|
465
|
+
if (isJsonLdNode(value)) {
|
|
466
|
+
const proofJsonLd = value["@context"] == null && jsonLd["@context"] != null ? {
|
|
467
|
+
"@context": jsonLd["@context"],
|
|
468
|
+
...value
|
|
469
|
+
} : value;
|
|
470
|
+
try {
|
|
471
|
+
parsed = await DataIntegrityProof.fromJsonLd(proofJsonLd, {
|
|
472
|
+
...options,
|
|
473
|
+
contextLoader: contexts?.loader ?? documentLoader
|
|
474
|
+
});
|
|
475
|
+
} catch {}
|
|
476
|
+
}
|
|
477
|
+
candidates.push({
|
|
478
|
+
value,
|
|
479
|
+
proof: parsed,
|
|
480
|
+
reference: getRawProofReference(value) ?? parsed?.id?.href,
|
|
481
|
+
declaredKeyId: contexts == null ? void 0 : await getAliasedDeclaredProofKeyId(value, jsonLd["@context"], contexts.replay)
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
return candidates;
|
|
485
|
+
}
|
|
486
|
+
async function findRawProofCandidate(jsonLd, proof, options, documentLoader) {
|
|
487
|
+
const candidates = await parseRawProofCandidates(jsonLd, await getRawProofValues(jsonLd, documentLoader), options, documentLoader);
|
|
488
|
+
const matches = [];
|
|
489
|
+
for (const candidate of candidates) if (candidate.proof != null && sameProof(candidate.proof, proof)) matches.push(candidate);
|
|
490
|
+
if (matches.length < 1) return void 0;
|
|
491
|
+
const first = matches[0];
|
|
492
|
+
const configurations = await Promise.all(matches.map((candidate) => normalizeProofConfiguration(candidate.value, jsonLd["@context"], documentLoader)));
|
|
493
|
+
const firstConfiguration = configurations[0];
|
|
494
|
+
return firstConfiguration != null && configurations.every((configuration) => configuration != null && equalJsonValues(configuration.value, firstConfiguration.value)) ? first : null;
|
|
495
|
+
}
|
|
496
|
+
function takeRawProofCandidate(pool, proof) {
|
|
497
|
+
for (let index = 0; index < pool.candidates.length; index++) {
|
|
498
|
+
if (pool.used.has(index)) continue;
|
|
499
|
+
const candidate = pool.candidates[index];
|
|
500
|
+
if (candidate.proof != null && sameProof(candidate.proof, proof)) {
|
|
501
|
+
pool.used.add(index);
|
|
502
|
+
return candidate;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const STANDARD_COMPACT_PROOF_PROPERTIES = /* @__PURE__ */ new Set([
|
|
507
|
+
"@context",
|
|
508
|
+
"type",
|
|
509
|
+
"cryptosuite",
|
|
510
|
+
"verificationMethod",
|
|
511
|
+
"proofPurpose",
|
|
512
|
+
"proofValue",
|
|
513
|
+
"created"
|
|
514
|
+
]);
|
|
515
|
+
const STANDARD_EXPANDED_PROOF_PROPERTIES = /* @__PURE__ */ new Set([
|
|
516
|
+
"@context",
|
|
517
|
+
"@type",
|
|
518
|
+
`${SECURITY_NAMESPACE}cryptosuite`,
|
|
519
|
+
SECURITY_VERIFICATION_METHOD,
|
|
520
|
+
`${SECURITY_NAMESPACE}proofPurpose`,
|
|
521
|
+
`${SECURITY_NAMESPACE}proofValue`,
|
|
522
|
+
"http://purl.org/dc/terms/created"
|
|
523
|
+
]);
|
|
524
|
+
function hasAdditionalProofOptions(value) {
|
|
525
|
+
const node = Array.isArray(value) && value.length === 1 ? value[0] : value;
|
|
526
|
+
return isJsonLdNode(node) && globalThis.Object.keys(node).some((property) => !STANDARD_COMPACT_PROOF_PROPERTIES.has(property) && !STANDARD_EXPANDED_PROOF_PROPERTIES.has(property));
|
|
527
|
+
}
|
|
528
|
+
function isExpandedJsonLdNode(value) {
|
|
529
|
+
const properties = globalThis.Object.keys(value).filter((key) => !key.startsWith("@"));
|
|
530
|
+
return properties.length > 0 && properties.every((property) => URL.canParse(property));
|
|
531
|
+
}
|
|
532
|
+
async function normalizeProofConfiguration(rawProof, documentContext, documentLoader) {
|
|
533
|
+
let node = Array.isArray(rawProof) && rawProof.length === 1 ? rawProof[0] : rawProof;
|
|
534
|
+
if (!isJsonLdNode(node)) return null;
|
|
535
|
+
if (isExpandedJsonLdNode(node)) {
|
|
536
|
+
if (documentContext == null) return null;
|
|
537
|
+
node = await jsonld.compact(node, documentContext, { documentLoader });
|
|
538
|
+
if (!isJsonLdNode(node)) return null;
|
|
539
|
+
} else node = structuredClone(node);
|
|
540
|
+
const receivedContext = node["@context"];
|
|
541
|
+
if (receivedContext != null && !contextStartsWith(documentContext, receivedContext)) return null;
|
|
542
|
+
const context = receivedContext ?? documentContext;
|
|
543
|
+
if (context != null) node["@context"] = structuredClone(context);
|
|
544
|
+
const proofValueProperties = await getJsonLdPropertyNames(node, SECURITY_PROOF_VALUE, ["proofValue", SECURITY_PROOF_VALUE], documentLoader, context) ?? /* @__PURE__ */ new Set(["proofValue", SECURITY_PROOF_VALUE]);
|
|
545
|
+
for (const property of proofValueProperties) delete node[property];
|
|
546
|
+
return {
|
|
547
|
+
value: node,
|
|
548
|
+
context
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
async function createProofConfiguration(jsonLd, proof, options, documentLoader, rawProofCandidate) {
|
|
552
|
+
let rawProof;
|
|
553
|
+
if (rawProofCandidate == null) {
|
|
554
|
+
const match = await findRawProofCandidate(jsonLd, proof, options, documentLoader);
|
|
555
|
+
if (match === null) return null;
|
|
556
|
+
rawProof = match?.value;
|
|
557
|
+
} else rawProof = rawProofCandidate.value;
|
|
558
|
+
if (rawProof == null) {
|
|
559
|
+
const serializedProof = await proof.toJsonLd();
|
|
560
|
+
if (hasAdditionalProofOptions(serializedProof)) rawProof = serializedProof;
|
|
561
|
+
}
|
|
562
|
+
if (rawProof != null) return await normalizeProofConfiguration(rawProof, jsonLd["@context"], documentLoader);
|
|
563
|
+
const context = jsonLd["@context"];
|
|
564
|
+
return {
|
|
565
|
+
value: {
|
|
566
|
+
...context == null ? {} : { "@context": context },
|
|
567
|
+
type: "DataIntegrityProof",
|
|
568
|
+
cryptosuite: proof.cryptosuite,
|
|
569
|
+
verificationMethod: proof.verificationMethodId.href,
|
|
570
|
+
proofPurpose: proof.proofPurpose,
|
|
571
|
+
created: proof.created.toString()
|
|
572
|
+
},
|
|
573
|
+
context
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
const KNOWN_PROOF_CONFIGURATION_PROPERTIES = /* @__PURE__ */ new Set([
|
|
577
|
+
"@context",
|
|
578
|
+
"@id",
|
|
579
|
+
"@type",
|
|
580
|
+
"id",
|
|
581
|
+
"type",
|
|
582
|
+
"cryptosuite",
|
|
583
|
+
`${SECURITY_NAMESPACE}cryptosuite`,
|
|
584
|
+
"verificationMethod",
|
|
585
|
+
SECURITY_VERIFICATION_METHOD,
|
|
586
|
+
"proofPurpose",
|
|
587
|
+
`${SECURITY_NAMESPACE}proofPurpose`,
|
|
588
|
+
"created",
|
|
589
|
+
"http://purl.org/dc/terms/created",
|
|
590
|
+
"expires",
|
|
591
|
+
SECURITY_EXPIRATION,
|
|
592
|
+
"domain",
|
|
593
|
+
SECURITY_DOMAIN,
|
|
594
|
+
"challenge",
|
|
595
|
+
SECURITY_CHALLENGE,
|
|
596
|
+
"nonce",
|
|
597
|
+
SECURITY_NONCE,
|
|
598
|
+
"previousProof",
|
|
599
|
+
SECURITY_PREVIOUS_PROOF
|
|
600
|
+
]);
|
|
601
|
+
async function getProofOption(proofConfig, propertyIri, defaults, documentLoader) {
|
|
602
|
+
let names = await getJsonLdPropertyNames(proofConfig, propertyIri, defaults, documentLoader, proofConfig["@context"], true);
|
|
603
|
+
if (names == null) {
|
|
604
|
+
if (globalThis.Object.keys(proofConfig).some((property) => !KNOWN_PROOF_CONFIGURATION_PROPERTIES.has(property))) return null;
|
|
605
|
+
names = new Set(defaults);
|
|
606
|
+
}
|
|
607
|
+
const present = globalThis.Object.keys(proofConfig).filter((property) => names.has(property));
|
|
608
|
+
if (present.length > 1) return null;
|
|
609
|
+
return present.length < 1 ? { present: false } : {
|
|
610
|
+
present: true,
|
|
611
|
+
value: proofConfig[present[0]]
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
function parseStringSet(value) {
|
|
615
|
+
if (typeof value === "string") return /* @__PURE__ */ new Set([value]);
|
|
616
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return null;
|
|
617
|
+
return new Set(value);
|
|
618
|
+
}
|
|
619
|
+
function equalStringSets(left, right) {
|
|
620
|
+
return left.size === right.size && [...left].every((value) => right.has(value));
|
|
621
|
+
}
|
|
622
|
+
async function hasValidProofOptions(proofConfig, options, documentLoader) {
|
|
623
|
+
const expires = await getProofOption(proofConfig, SECURITY_EXPIRATION, ["expires", SECURITY_EXPIRATION], documentLoader);
|
|
624
|
+
if (expires == null) return null;
|
|
625
|
+
let expiration;
|
|
626
|
+
if (expires.present) {
|
|
627
|
+
if (typeof expires.value !== "string") return null;
|
|
628
|
+
try {
|
|
629
|
+
expiration = Temporal.Instant.from(expires.value);
|
|
630
|
+
} catch {
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
if (Temporal.Instant.compare(Temporal.Now.instant(), expiration) >= 0) return null;
|
|
634
|
+
}
|
|
635
|
+
const domain = await getProofOption(proofConfig, SECURITY_DOMAIN, ["domain", SECURITY_DOMAIN], documentLoader);
|
|
636
|
+
if (domain == null) return null;
|
|
637
|
+
const proofDomains = domain.present ? parseStringSet(domain.value) : null;
|
|
638
|
+
if (domain.present && proofDomains == null) return null;
|
|
639
|
+
if (options.domain != null) {
|
|
640
|
+
const expectedDomains = parseStringSet(options.domain);
|
|
641
|
+
if (expectedDomains == null || proofDomains == null || !equalStringSets(proofDomains, expectedDomains)) return null;
|
|
642
|
+
}
|
|
643
|
+
const challenge = await getProofOption(proofConfig, SECURITY_CHALLENGE, ["challenge", SECURITY_CHALLENGE], documentLoader);
|
|
644
|
+
if (challenge == null || challenge.present && typeof challenge.value !== "string" || options.challenge != null && (!challenge.present || challenge.value !== options.challenge)) return null;
|
|
645
|
+
const nonce = await getProofOption(proofConfig, SECURITY_NONCE, ["nonce", SECURITY_NONCE], documentLoader);
|
|
646
|
+
if (nonce == null || nonce.present && typeof nonce.value !== "string") return null;
|
|
647
|
+
const previousProof = await getProofOption(proofConfig, SECURITY_PREVIOUS_PROOF, ["previousProof", SECURITY_PREVIOUS_PROOF], documentLoader);
|
|
648
|
+
if (previousProof == null || previousProof.present && typeof previousProof.value !== "string" && (!Array.isArray(previousProof.value) || previousProof.value.some((item) => typeof item !== "string"))) return null;
|
|
649
|
+
return { expires: expiration };
|
|
650
|
+
}
|
|
651
|
+
async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller = false) {
|
|
652
|
+
if (!isJsonLdNode(jsonLd) || proof.cryptosuite !== "eddsa-jcs-2022" || proof.verificationMethodId == null || proof.proofPurpose !== "assertionMethod" || proof.proofValue == null || proof.created == null) return null;
|
|
653
|
+
const proofContextLoader = messageDigestCache.proofContextLoader ?? preloadedOnlyDocumentLoader;
|
|
654
|
+
const proofConfiguration = await createProofConfiguration(jsonLd, proof, options, proofContextLoader, rawProofCandidate);
|
|
655
|
+
if (proofConfiguration == null) return null;
|
|
656
|
+
const validProofOptions = await hasValidProofOptions(proofConfiguration.value, options, proofContextLoader);
|
|
657
|
+
if (validProofOptions == null) return null;
|
|
658
|
+
const { expires } = validProofOptions;
|
|
659
|
+
if (expires != null && (messageDigestCache.expires == null || Temporal.Instant.compare(expires, messageDigestCache.expires) < 0)) messageDigestCache.expires = expires;
|
|
660
|
+
const publicKeyPromise = measureSignatureKeyFetch(options.meterProvider, "object_integrity", () => fetchKey(proof.verificationMethodId, Multikey, {
|
|
661
|
+
...options,
|
|
662
|
+
keyIdBoundByCaller
|
|
663
|
+
}));
|
|
664
|
+
const proofBytes = new TextEncoder().encode(serialize(proofConfiguration.value));
|
|
665
|
+
const proofDigest = await crypto.subtle.digest("SHA-256", proofBytes);
|
|
666
|
+
let fetchedKey;
|
|
667
|
+
try {
|
|
668
|
+
fetchedKey = await publicKeyPromise;
|
|
669
|
+
} catch (error) {
|
|
670
|
+
logger.debug("Failed to get the key (verificationMethod) for the proof:\n{proof}", {
|
|
671
|
+
proof,
|
|
672
|
+
keyId: proof.verificationMethodId.href,
|
|
673
|
+
error
|
|
674
|
+
});
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
const publicKey = fetchedKey.key;
|
|
678
|
+
if (publicKey == null) {
|
|
679
|
+
logger.debug("Failed to get the key (verificationMethod) for the proof:\n{proof}", {
|
|
680
|
+
proof,
|
|
681
|
+
keyId: proof.verificationMethodId.href
|
|
682
|
+
});
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
if (publicKey.publicKey.algorithm.name !== "Ed25519") {
|
|
686
|
+
if (fetchedKey.cached) {
|
|
687
|
+
logger.debug("The cached key (verificationMethod) for the proof is not a valid Ed25519 key:\n{keyId}; retrying with the freshly fetched key...", {
|
|
688
|
+
proof,
|
|
689
|
+
keyId: proof.verificationMethodId.href
|
|
690
|
+
});
|
|
691
|
+
return await verifyProofInternal(jsonLd, proof, {
|
|
692
|
+
...options,
|
|
693
|
+
keyCache: bypassKeyCacheReads(options.keyCache)
|
|
694
|
+
}, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
695
|
+
}
|
|
696
|
+
logger.debug("The fetched key (verificationMethod) for the proof is not a valid Ed25519 key:\n{keyId}", {
|
|
697
|
+
proof,
|
|
698
|
+
keyId: proof.verificationMethodId.href
|
|
699
|
+
});
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
const digest = new Uint8Array(proofDigest.byteLength + 32);
|
|
703
|
+
digest.set(new Uint8Array(proofDigest), 0);
|
|
704
|
+
const proofValue = proof.proofValue;
|
|
705
|
+
let keyTried = false;
|
|
706
|
+
const verifyCandidate = async (msgDigest) => {
|
|
707
|
+
digest.set(new Uint8Array(msgDigest), proofDigest.byteLength);
|
|
708
|
+
if (!keyTried) {
|
|
709
|
+
triedKey(options, publicKey);
|
|
710
|
+
keyTried = true;
|
|
711
|
+
}
|
|
712
|
+
return await crypto.subtle.verify("Ed25519", publicKey.publicKey, proofValue.slice(), digest);
|
|
713
|
+
};
|
|
714
|
+
const messageDigestKey = serialize(proofConfiguration.context ?? null);
|
|
715
|
+
const messageDigestValues = messageDigestCache.values ??= /* @__PURE__ */ new Map();
|
|
716
|
+
let messageDigestsPromise = messageDigestValues.get(messageDigestKey);
|
|
717
|
+
if (messageDigestsPromise == null) {
|
|
718
|
+
messageDigestsPromise = createProofMessageDigests(jsonLd, messageDigestCache.proofContextLoader, proofConfiguration.context, messageDigestCache.proofPropertyMode);
|
|
719
|
+
messageDigestValues.set(messageDigestKey, messageDigestsPromise);
|
|
720
|
+
}
|
|
721
|
+
const messageDigests = await messageDigestsPromise;
|
|
722
|
+
if (await verifyCandidate(messageDigests.onWire)) return publicKey;
|
|
723
|
+
if (messageDigestCache.proofPropertyMode !== "literal") {
|
|
724
|
+
const normalizedDigest = await messageDigests.normalized();
|
|
725
|
+
if (normalizedDigest != null && await verifyCandidate(normalizedDigest)) return publicKey;
|
|
726
|
+
}
|
|
727
|
+
if (fetchedKey.cached) {
|
|
728
|
+
logger.debug("Failed to verify the proof with the cached key {keyId}; retrying with the freshly fetched key...", {
|
|
729
|
+
keyId: proof.verificationMethodId.href,
|
|
730
|
+
proof
|
|
731
|
+
});
|
|
732
|
+
return await verifyProofInternal(jsonLd, proof, {
|
|
733
|
+
...options,
|
|
734
|
+
keyCache: bypassKeyCacheReads(options.keyCache)
|
|
735
|
+
}, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
736
|
+
}
|
|
737
|
+
logger.debug("Failed to verify the proof with the fetched key {keyId}:\n{proof}", {
|
|
738
|
+
keyId: proof.verificationMethodId.href,
|
|
739
|
+
proof
|
|
740
|
+
});
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
const AS_NAMESPACE = "https://www.w3.org/ns/activitystreams#";
|
|
744
|
+
const FEP_2277_ACTOR_PROPERTIES = ["http://www.w3.org/ns/ldp#inbox", `${AS_NAMESPACE}outbox`];
|
|
745
|
+
const FEP_2277_COLLECTION_PROPERTIES = [
|
|
746
|
+
"items",
|
|
747
|
+
"orderedItems",
|
|
748
|
+
"totalItems",
|
|
749
|
+
"partOf",
|
|
750
|
+
"first",
|
|
751
|
+
"last",
|
|
752
|
+
"next",
|
|
753
|
+
"prev",
|
|
754
|
+
"current"
|
|
755
|
+
].map((property) => AS_NAMESPACE + property);
|
|
756
|
+
const PORTABLE_OBJECT_ID_PATTERN = /^ap(?:\+ef61)?:\/\//i;
|
|
757
|
+
const FUNCTIONAL_PROOF_PROPERTIES = [
|
|
758
|
+
`${SECURITY_NAMESPACE}cryptosuite`,
|
|
759
|
+
SECURITY_VERIFICATION_METHOD,
|
|
760
|
+
`${SECURITY_NAMESPACE}proofPurpose`,
|
|
761
|
+
`${SECURITY_NAMESPACE}proofValue`,
|
|
762
|
+
"http://purl.org/dc/terms/created"
|
|
763
|
+
];
|
|
764
|
+
function isJsonLdNode(value) {
|
|
765
|
+
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
766
|
+
}
|
|
767
|
+
function hasValidPortableProofShape(proofValue) {
|
|
768
|
+
if (!isJsonLdNode(proofValue)) return false;
|
|
769
|
+
let proofNode = proofValue;
|
|
770
|
+
if ("@graph" in proofNode) {
|
|
771
|
+
const graph = proofNode["@graph"];
|
|
772
|
+
if (!Array.isArray(graph) || graph.length !== 1 || !isJsonLdNode(graph[0])) return false;
|
|
773
|
+
proofNode = graph[0];
|
|
774
|
+
}
|
|
775
|
+
const types = proofNode["@type"];
|
|
776
|
+
return Array.isArray(types) && types.length === 1 && types[0] === DATA_INTEGRITY_PROOF && FUNCTIONAL_PROOF_PROPERTIES.every((property) => {
|
|
777
|
+
const values = proofNode[property];
|
|
778
|
+
return Array.isArray(values) && values.length === 1 && !(isJsonLdNode(values[0]) && "@list" in values[0]);
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Checks whether an expanded portable actor node has the `gateways` that
|
|
783
|
+
* FEP-ef61 requires: a non-empty list whose items are all HTTP(S) URIs with
|
|
784
|
+
* an empty path, query, and fragment. Like the generated vocabulary decoder,
|
|
785
|
+
* this also tolerates a plain set of values instead of a `@list`, and gateway
|
|
786
|
+
* strings given as `@value`s instead of `@id`s. JCS-authenticated actors may
|
|
787
|
+
* also supply an unmapped literal `gateways` list.
|
|
788
|
+
*/
|
|
789
|
+
function hasValidPortableActorGateways(node, allowUnmapped) {
|
|
790
|
+
const values = getPortableActorGateways(node, allowUnmapped);
|
|
791
|
+
if (!Array.isArray(values)) return false;
|
|
792
|
+
let items = values;
|
|
793
|
+
if (values.some((value) => isJsonLdNode(value) && "@list" in value)) {
|
|
794
|
+
if (values.length !== 1 || !isJsonLdNode(values[0])) return false;
|
|
795
|
+
items = values[0]["@list"];
|
|
796
|
+
}
|
|
797
|
+
if (!Array.isArray(items) || items.length < 1) return false;
|
|
798
|
+
return items.every((item) => {
|
|
799
|
+
if (!isJsonLdNode(item)) return false;
|
|
800
|
+
const gateway = typeof item["@id"] === "string" ? item["@id"] : item["@value"];
|
|
801
|
+
if (typeof gateway !== "string") return false;
|
|
802
|
+
try {
|
|
803
|
+
parseGatewayUrl(gateway);
|
|
804
|
+
} catch (error) {
|
|
805
|
+
if (error instanceof TypeError) return false;
|
|
806
|
+
throw error;
|
|
807
|
+
}
|
|
808
|
+
return true;
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Classifies an expanded JSON-LD node into an FEP-2277 core type by the
|
|
813
|
+
* properties it has.
|
|
814
|
+
* @internal
|
|
815
|
+
*/
|
|
816
|
+
function classifyFep2277CoreType(node) {
|
|
817
|
+
if (FEP_2277_ACTOR_PROPERTIES.every((property) => property in node)) return "actor";
|
|
818
|
+
if (`${SECURITY_NAMESPACE}publicKeyMultibase` in node) return "verificationMethod";
|
|
819
|
+
if (`${SECURITY_NAMESPACE}publicKeyPem` in node) return "publicKey";
|
|
820
|
+
if (`${AS_NAMESPACE}href` in node) return "link";
|
|
821
|
+
if (`${AS_NAMESPACE}actor` in node) return "activity";
|
|
822
|
+
if (FEP_2277_COLLECTION_PROPERTIES.some((property) => property in node)) return "collection";
|
|
823
|
+
return "object";
|
|
824
|
+
}
|
|
825
|
+
async function expandPortableObjectRoot(jsonLd, contextLoader) {
|
|
826
|
+
if (!isJsonLdNode(jsonLd)) throw new TypeError("Expected a single JSON-LD object.");
|
|
827
|
+
const loadedContexts = /* @__PURE__ */ new Map();
|
|
828
|
+
const loader = getNormalizationContextLoader(contextLoader);
|
|
829
|
+
const recordingLoader = async (url, options) => {
|
|
830
|
+
const remoteDocument = await loader(url, options);
|
|
831
|
+
const key = URL.canParse(url) ? new URL(url).href : url;
|
|
832
|
+
loadedContexts.set(key, structuredClone(remoteDocument));
|
|
833
|
+
return remoteDocument;
|
|
834
|
+
};
|
|
835
|
+
const expanded = await jsonld.expand(jsonLd, {
|
|
836
|
+
documentLoader: recordingLoader,
|
|
837
|
+
keepFreeFloatingNodes: true
|
|
838
|
+
});
|
|
839
|
+
if (expanded.length !== 1 || !isJsonLdNode(expanded[0])) throw new TypeError("Expected a single JSON-LD object.");
|
|
840
|
+
return {
|
|
841
|
+
root: expanded[0],
|
|
842
|
+
proofContextLoader: async (url, options) => {
|
|
843
|
+
const key = URL.canParse(url) ? new URL(url).href : url;
|
|
844
|
+
const remoteDocument = loadedContexts.get(key);
|
|
845
|
+
if (remoteDocument != null) return structuredClone(remoteDocument);
|
|
846
|
+
return await preloadedOnlyDocumentLoader(url, options);
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Gets the DID of a portable object ID, which is either an `ap:` or
|
|
852
|
+
* `ap+ef61:` URI or an FEP-ef61 compatible identifier.
|
|
853
|
+
* @throws {TypeError} If the ID is malformed.
|
|
854
|
+
*/
|
|
855
|
+
function getObjectDid(id) {
|
|
856
|
+
try {
|
|
857
|
+
parseIri(id);
|
|
858
|
+
if (PORTABLE_OBJECT_ID_PATTERN.test(id)) return getFe34Origin(id);
|
|
859
|
+
const portableId = fromCompatibleEf61Id(id);
|
|
860
|
+
if (portableId != null) return getFe34Origin(portableId);
|
|
861
|
+
} catch (error) {
|
|
862
|
+
if (!(error instanceof TypeError)) throw error;
|
|
863
|
+
throw new InvalidPortableObjectIdError(id, { cause: error });
|
|
864
|
+
}
|
|
865
|
+
throw new InvalidPortableObjectIdError(id);
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* The error thrown when a document's ID looks portable, i.e., it is an `ap:`
|
|
869
|
+
* or `ap+ef61:` URI or looks like an FEP-ef61 compatible identifier, but is
|
|
870
|
+
* malformed.
|
|
871
|
+
* @internal
|
|
872
|
+
*/
|
|
873
|
+
var InvalidPortableObjectIdError = class extends TypeError {
|
|
874
|
+
constructor(id, options) {
|
|
875
|
+
super(`Invalid portable object ID: ${id}`, options);
|
|
876
|
+
this.name = "InvalidPortableObjectIdError";
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
async function preparePortableObjectProof(jsonLd, options) {
|
|
880
|
+
if (isJsonLdNode(jsonLd) && typeof jsonLd["@id"] === "string" && !isPortableId(jsonLd["@id"])) return {
|
|
881
|
+
prepared: false,
|
|
882
|
+
result: {
|
|
883
|
+
verified: false,
|
|
884
|
+
reason: { type: "notPortableObject" }
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
const { root, proofContextLoader } = await expandPortableObjectRoot(jsonLd, options.contextLoader);
|
|
888
|
+
const id = root["@id"];
|
|
889
|
+
if (typeof id !== "string" || !isPortableId(id)) return {
|
|
890
|
+
prepared: false,
|
|
891
|
+
result: {
|
|
892
|
+
verified: false,
|
|
893
|
+
reason: { type: "notPortableObject" }
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
const objectDid = getObjectDid(id);
|
|
897
|
+
const objectId = parseIri(id);
|
|
898
|
+
const objectType = classifyFep2277CoreType(root);
|
|
899
|
+
if (objectType === "verificationMethod" || objectType === "publicKey" || objectType === "link") return {
|
|
900
|
+
prepared: false,
|
|
901
|
+
result: {
|
|
902
|
+
verified: false,
|
|
903
|
+
reason: {
|
|
904
|
+
type: "unsupportedObjectType",
|
|
905
|
+
objectType
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
const proofValues = root[SECURITY_PROOF];
|
|
910
|
+
if (proofValues == null || Array.isArray(proofValues) && proofValues.length < 1) return {
|
|
911
|
+
prepared: false,
|
|
912
|
+
root,
|
|
913
|
+
objectType,
|
|
914
|
+
result: objectType === "collection" ? {
|
|
915
|
+
verified: false,
|
|
916
|
+
reason: { type: "unsecuredCollection" }
|
|
917
|
+
} : {
|
|
918
|
+
verified: false,
|
|
919
|
+
reason: { type: "missingProof" }
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
if (!Array.isArray(proofValues)) return {
|
|
923
|
+
prepared: false,
|
|
924
|
+
result: {
|
|
925
|
+
verified: false,
|
|
926
|
+
reason: {
|
|
927
|
+
type: "invalidProof",
|
|
928
|
+
proofIndex: 0
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
const rawProofValues = isJsonLdNode(jsonLd) ? await getRawProofValues(jsonLd, proofContextLoader) : [];
|
|
933
|
+
const proofs = [];
|
|
934
|
+
for (let proofIndex = 0; proofIndex < proofValues.length; proofIndex++) {
|
|
935
|
+
const proofValue = proofValues[proofIndex];
|
|
936
|
+
if (!hasValidPortableProofShape(proofValue)) return {
|
|
937
|
+
prepared: false,
|
|
938
|
+
result: {
|
|
939
|
+
verified: false,
|
|
940
|
+
reason: {
|
|
941
|
+
type: "invalidProof",
|
|
942
|
+
proofIndex
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
let proof;
|
|
947
|
+
try {
|
|
948
|
+
proof = await DataIntegrityProof.fromJsonLd(proofValue, options);
|
|
949
|
+
} catch {
|
|
950
|
+
return {
|
|
951
|
+
prepared: false,
|
|
952
|
+
result: {
|
|
953
|
+
verified: false,
|
|
954
|
+
reason: {
|
|
955
|
+
type: "invalidProof",
|
|
956
|
+
proofIndex
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
proofs.push(proof);
|
|
962
|
+
}
|
|
963
|
+
for (let proofIndex = 0; proofIndex < proofs.length; proofIndex++) {
|
|
964
|
+
const verificationMethod = proofs[proofIndex].verificationMethodId;
|
|
965
|
+
if (verificationMethod == null) return {
|
|
966
|
+
prepared: false,
|
|
967
|
+
result: {
|
|
968
|
+
verified: false,
|
|
969
|
+
reason: {
|
|
970
|
+
type: "invalidProof",
|
|
971
|
+
proofIndex
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
if (verificationMethod.protocol !== "did:") return {
|
|
976
|
+
prepared: false,
|
|
977
|
+
result: {
|
|
978
|
+
verified: false,
|
|
979
|
+
reason: {
|
|
980
|
+
type: "unsupportedVerificationMethod",
|
|
981
|
+
proofIndex,
|
|
982
|
+
verificationMethod
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
try {
|
|
987
|
+
getFe34Origin(verificationMethod);
|
|
988
|
+
} catch (error) {
|
|
989
|
+
if (!(error instanceof TypeError)) throw error;
|
|
990
|
+
return {
|
|
991
|
+
prepared: false,
|
|
992
|
+
result: {
|
|
993
|
+
verified: false,
|
|
994
|
+
reason: {
|
|
995
|
+
type: "unsupportedVerificationMethod",
|
|
996
|
+
proofIndex,
|
|
997
|
+
verificationMethod
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
if (getFe34Origin(verificationMethod) !== objectDid) return {
|
|
1003
|
+
prepared: false,
|
|
1004
|
+
result: {
|
|
1005
|
+
verified: false,
|
|
1006
|
+
reason: {
|
|
1007
|
+
type: "verificationMethodMismatch",
|
|
1008
|
+
proofIndex,
|
|
1009
|
+
objectId,
|
|
1010
|
+
verificationMethod
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
if (objectType === "actor" && !hasValidPortableActorGateways(root, proofs.every((proof) => proof.cryptosuite === "eddsa-jcs-2022"))) return {
|
|
1016
|
+
prepared: false,
|
|
1017
|
+
root,
|
|
1018
|
+
objectType,
|
|
1019
|
+
result: {
|
|
1020
|
+
verified: false,
|
|
1021
|
+
reason: { type: "invalidGateways" }
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
return {
|
|
1025
|
+
prepared: true,
|
|
1026
|
+
root,
|
|
1027
|
+
objectType,
|
|
1028
|
+
objectId,
|
|
1029
|
+
proofs,
|
|
1030
|
+
rawProofValues,
|
|
1031
|
+
proofContextLoader
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Applies portable-object proof policy to one map-local cryptographic result.
|
|
1036
|
+
*
|
|
1037
|
+
* @internal
|
|
1038
|
+
*/
|
|
1039
|
+
async function verifyPortableObjectProofPolicy(jsonLd, key, options = {}) {
|
|
1040
|
+
const policyOptions = {
|
|
1041
|
+
...options,
|
|
1042
|
+
contextLoader: preloadedOnlyDocumentLoader
|
|
1043
|
+
};
|
|
1044
|
+
const prepared = await preparePortableObjectProof(jsonLd, policyOptions);
|
|
1045
|
+
if (!prepared.prepared) return prepared.result;
|
|
1046
|
+
const policyProof = prepared.proofs[0];
|
|
1047
|
+
const literalProof = isJsonLdNode(jsonLd) && isJsonLdNode(jsonLd.proof) ? (await parseRawProofCandidates(jsonLd, [jsonLd.proof], policyOptions, prepared.proofContextLoader))[0]?.proof : null;
|
|
1048
|
+
const verificationMethod = policyProof?.verificationMethodId;
|
|
1049
|
+
if (prepared.proofs.length !== 1 || key == null || policyProof == null || literalProof == null || !sameProof(policyProof, literalProof) || verificationMethod == null || key.id?.href !== verificationMethod.href) return {
|
|
1050
|
+
verified: false,
|
|
1051
|
+
reason: {
|
|
1052
|
+
type: "invalidProof",
|
|
1053
|
+
proofIndex: prepared.proofs.length > 1 ? 1 : 0
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
return {
|
|
1057
|
+
verified: true,
|
|
1058
|
+
keys: [key],
|
|
1059
|
+
objectId: formatIri(prepared.objectId)
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Verifies the FEP-ef61 Object Integrity Proof policy for a portable object.
|
|
1064
|
+
*
|
|
1065
|
+
* This applies the FEP-2277 core-type classification to the top-level JSON-LD
|
|
1066
|
+
* node. Portable actors, activities, and objects require proofs. A portable
|
|
1067
|
+
* collection without a proof is reported separately so a caller can apply a
|
|
1068
|
+
* gateway trust policy. Embedded portable objects are not traversed.
|
|
1069
|
+
*
|
|
1070
|
+
* A portable object is a document whose ID is an `ap:` or `ap+ef61:` URI, or
|
|
1071
|
+
* an [FEP-ef61] compatible identifier such as
|
|
1072
|
+
* `https://gw.example/.well-known/apgateway/did:key:z6Mk…/actor`, which
|
|
1073
|
+
* stands for the portable ID `ap+ef61://did:key:z6Mk…/actor`.
|
|
1074
|
+
*
|
|
1075
|
+
* Every proof must use a DID URL whose DID matches the portable object's
|
|
1076
|
+
* authority, i.e., the DID of its canonical portable ID, and every proof must
|
|
1077
|
+
* pass {@link verifyProof}. A portable actor must also have a non-empty
|
|
1078
|
+
* `gateways` list whose items are all HTTP(S) URIs with an empty path, query,
|
|
1079
|
+
* and fragment; otherwise, it is rejected with the `invalidGateways` reason
|
|
1080
|
+
* before its proofs are verified. The proofs are verified over the document as
|
|
1081
|
+
* given; a compatible identifier is never rewritten into a portable ID.
|
|
1082
|
+
* An unmapped literal `gateways` property is also accepted when every proof
|
|
1083
|
+
* uses `eddsa-jcs-2022`, which authenticates the original JSON properties.
|
|
1084
|
+
*
|
|
1085
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
1086
|
+
*
|
|
1087
|
+
* @param jsonLd The JSON-LD document to verify.
|
|
1088
|
+
* @param options Additional options. See also
|
|
1089
|
+
* {@link VerifyPortableObjectProofOptions}.
|
|
1090
|
+
* @returns The detailed portable proof-policy result.
|
|
1091
|
+
* @throws {TypeError} If the input is not a single JSON-LD object or has a
|
|
1092
|
+
* malformed portable ID or compatible identifier.
|
|
1093
|
+
* @since 2.4.0
|
|
1094
|
+
*/
|
|
1095
|
+
async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
1096
|
+
return (await verifyPortableObjectProofWithRoot(jsonLd, options)).result;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Same as {@link verifyPortableObjectProof}, but also returns the expanded
|
|
1100
|
+
* root node and its FEP-2277 core type, so that a caller can apply further
|
|
1101
|
+
* policy, such as the gateway trust policy for unsecured collections, to the
|
|
1102
|
+
* same interpretation of the document.
|
|
1103
|
+
* @internal
|
|
1104
|
+
*/
|
|
1105
|
+
async function verifyPortableObjectProofWithRoot(jsonLd, options = {}) {
|
|
1106
|
+
const prepared = await preparePortableObjectProof(jsonLd, options);
|
|
1107
|
+
if (!prepared.prepared) return {
|
|
1108
|
+
result: prepared.result,
|
|
1109
|
+
...prepared.root == null ? {} : { root: prepared.root },
|
|
1110
|
+
...prepared.objectType == null ? {} : { objectType: prepared.objectType }
|
|
1111
|
+
};
|
|
1112
|
+
const { root, objectType, proofContextLoader } = prepared;
|
|
1113
|
+
const messageDigestCache = { proofContextLoader };
|
|
1114
|
+
const result = await verifyPreparedPortableObjectProof(jsonLd, prepared, options, messageDigestCache);
|
|
1115
|
+
const { expires } = messageDigestCache;
|
|
1116
|
+
return {
|
|
1117
|
+
result,
|
|
1118
|
+
root,
|
|
1119
|
+
objectType,
|
|
1120
|
+
...result.verified && expires != null ? { expires } : {}
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
async function verifyPreparedPortableObjectProof(jsonLd, prepared, options, messageDigestCache) {
|
|
1124
|
+
const { proofs, rawProofValues, proofContextLoader } = prepared;
|
|
1125
|
+
const keys = [];
|
|
1126
|
+
const rawProofCandidatePool = {
|
|
1127
|
+
candidates: await parseRawProofCandidates(jsonLd, rawProofValues, options, proofContextLoader),
|
|
1128
|
+
used: /* @__PURE__ */ new Set()
|
|
1129
|
+
};
|
|
1130
|
+
for (let proofIndex = 0; proofIndex < proofs.length; proofIndex++) {
|
|
1131
|
+
const rawProofCandidate = takeRawProofCandidate(rawProofCandidatePool, proofs[proofIndex]);
|
|
1132
|
+
if (rawProofCandidate == null) return {
|
|
1133
|
+
verified: false,
|
|
1134
|
+
reason: {
|
|
1135
|
+
type: "invalidProof",
|
|
1136
|
+
proofIndex
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
const key = await verifyProofWithMessageDigestCache(jsonLd, proofs[proofIndex], options, messageDigestCache, rawProofCandidate, true);
|
|
1140
|
+
if (key == null) return {
|
|
1141
|
+
verified: false,
|
|
1142
|
+
reason: {
|
|
1143
|
+
type: "invalidProof",
|
|
1144
|
+
proofIndex
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
keys.push(key);
|
|
1148
|
+
}
|
|
1149
|
+
return {
|
|
1150
|
+
verified: true,
|
|
1151
|
+
keys
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Verifies the given object. It will verify all the proofs in the object,
|
|
1156
|
+
* and succeed only if all the proofs are valid and all attributions and
|
|
1157
|
+
* actors are authenticated by the proofs.
|
|
1158
|
+
* @template T The type of the object to verify.
|
|
1159
|
+
* @param cls The class of the object to verify. It must be a subclass of
|
|
1160
|
+
* the {@link Object}.
|
|
1161
|
+
* @param jsonLd The JSON-LD object to verify. It's assumed that the object
|
|
1162
|
+
* is a compacted JSON-LD representation of a `T` with `@context`.
|
|
1163
|
+
* @param options Additional options. See also {@link VerifyObjectOptions}.
|
|
1164
|
+
* @returns The object if it's verified, or `null` if it's not.
|
|
1165
|
+
* @throws {TypeError} If the object is invalid or unsupported.
|
|
1166
|
+
* @since 0.10.0
|
|
1167
|
+
*/
|
|
1168
|
+
async function verifyObject(cls, jsonLd, options = {}) {
|
|
1169
|
+
if (options[verificationObservation]?.attempt == null) {
|
|
1170
|
+
const observedOptions = {
|
|
1171
|
+
...options,
|
|
1172
|
+
[verificationObservation]: options[verificationObservation] ?? {
|
|
1173
|
+
attempts: [],
|
|
1174
|
+
captureRawKeyIds: false
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify_object", async (span) => {
|
|
1178
|
+
try {
|
|
1179
|
+
return await observeAttempt(observedOptions, "objectIntegrity", async (observation) => {
|
|
1180
|
+
const object = await verifyObject(cls, jsonLd, {
|
|
1181
|
+
...options,
|
|
1182
|
+
[verificationObservation]: observation
|
|
1183
|
+
});
|
|
1184
|
+
if (object == null) {
|
|
1185
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
1186
|
+
span.setAttribute("activitypub.verification.failure_reason", observation.attempt?.reason?.type ?? "signatureVerificationFailed");
|
|
1187
|
+
}
|
|
1188
|
+
return object;
|
|
1189
|
+
}, (value) => value != null);
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
span.setStatus({
|
|
1192
|
+
code: SpanStatusCode.ERROR,
|
|
1193
|
+
message: String(error)
|
|
1194
|
+
});
|
|
1195
|
+
throw error;
|
|
1196
|
+
} finally {
|
|
1197
|
+
span.end();
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
const observation = options[verificationObservation];
|
|
1202
|
+
const logger = getLogger([
|
|
1203
|
+
"fedify",
|
|
1204
|
+
"sig",
|
|
1205
|
+
"proof"
|
|
1206
|
+
]);
|
|
1207
|
+
const object = await cls.fromJsonLd(jsonLd, options);
|
|
1208
|
+
observation?.parsedObject?.(object);
|
|
1209
|
+
if (observation?.subject != null) observation.subject.id = object.id == null ? null : new URL(object.id.href);
|
|
1210
|
+
const defaultDocumentLoader = getDocumentLoader();
|
|
1211
|
+
const proofContextLoader = options.contextLoader ?? defaultDocumentLoader;
|
|
1212
|
+
const attributions = new Set(object.attributionIds.map((uri) => uri.href));
|
|
1213
|
+
if (object instanceof Activity) for (const uri of object.actorIds) attributions.add(uri.href);
|
|
1214
|
+
const rawProofValues = isJsonLdNode(jsonLd) ? await getRawProofValues(jsonLd, proofContextLoader) : [];
|
|
1215
|
+
const rawProofCandidates = isJsonLdNode(jsonLd) ? await parseRawProofCandidates(jsonLd, rawProofValues, options, proofContextLoader) : [];
|
|
1216
|
+
const rawProofCandidatePool = {
|
|
1217
|
+
candidates: rawProofCandidates,
|
|
1218
|
+
used: /* @__PURE__ */ new Set()
|
|
1219
|
+
};
|
|
1220
|
+
const baseDocumentLoader = options.documentLoader ?? defaultDocumentLoader;
|
|
1221
|
+
const hydratedCandidates = /* @__PURE__ */ new Set();
|
|
1222
|
+
let loadedPortableProofs = 0;
|
|
1223
|
+
let acceptedPortableProofs = 0;
|
|
1224
|
+
const acceptPortableProofDocument = async () => {
|
|
1225
|
+
acceptedPortableProofs++;
|
|
1226
|
+
return { verified: true };
|
|
1227
|
+
};
|
|
1228
|
+
const proofDocumentLoader = async (url, loaderOptions) => {
|
|
1229
|
+
const remoteDocument = await baseDocumentLoader(url, loaderOptions);
|
|
1230
|
+
if (isPortableId(url)) loadedPortableProofs++;
|
|
1231
|
+
const reference = normalizeDocumentUrl(url);
|
|
1232
|
+
const candidateIndex = rawProofCandidates.findIndex((candidate, index) => !hydratedCandidates.has(index) && !rawProofCandidatePool.used.has(index) && candidate.reference === reference);
|
|
1233
|
+
if (candidateIndex >= 0) {
|
|
1234
|
+
hydratedCandidates.add(candidateIndex);
|
|
1235
|
+
let parsed = null;
|
|
1236
|
+
const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(proofContextLoader) : void 0;
|
|
1237
|
+
try {
|
|
1238
|
+
parsed = await DataIntegrityProof.fromJsonLd(remoteDocument.document, {
|
|
1239
|
+
documentLoader: baseDocumentLoader,
|
|
1240
|
+
contextLoader: contexts?.loader ?? proofContextLoader,
|
|
1241
|
+
tracerProvider: options.tracerProvider,
|
|
1242
|
+
baseUrl: parseIri(remoteDocument.documentUrl)
|
|
1243
|
+
});
|
|
1244
|
+
} catch {}
|
|
1245
|
+
rawProofCandidates[candidateIndex] = {
|
|
1246
|
+
value: structuredClone(remoteDocument.document),
|
|
1247
|
+
proof: parsed,
|
|
1248
|
+
reference,
|
|
1249
|
+
declaredKeyId: contexts == null ? void 0 : await getAliasedDeclaredProofKeyId(remoteDocument.document, void 0, contexts.replay)
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
return remoteDocument;
|
|
1253
|
+
};
|
|
1254
|
+
for await (const proof of object.getProofs({
|
|
1255
|
+
...options,
|
|
1256
|
+
documentLoader: proofDocumentLoader,
|
|
1257
|
+
gateways: [],
|
|
1258
|
+
verifyPortableObject: acceptPortableProofDocument,
|
|
1259
|
+
inheritPortableObjectVerifier: false
|
|
1260
|
+
})) {
|
|
1261
|
+
const rawProofCandidate = takeRawProofCandidate(rawProofCandidatePool, proof);
|
|
1262
|
+
if (rawProofCandidate == null) return null;
|
|
1263
|
+
const key = await verifyProofWithMessageDigestCache(jsonLd, proof, options, { proofContextLoader }, rawProofCandidate);
|
|
1264
|
+
if (key === null) return null;
|
|
1265
|
+
if (proof.verificationMethodId == null) return null;
|
|
1266
|
+
if (key.controllerId == null) {
|
|
1267
|
+
logger.debug("Key {keyId} does not have a controller.", { keyId: key.id?.href });
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
deleteAuthenticatedAttribution(attributions, key.controllerId, proof.verificationMethodId);
|
|
1271
|
+
}
|
|
1272
|
+
if (acceptedPortableProofs < loadedPortableProofs) {
|
|
1273
|
+
logger.debug("Some portable proof references could not be dereferenced.");
|
|
1274
|
+
return null;
|
|
1275
|
+
}
|
|
1276
|
+
if (attributions.size > 0) {
|
|
1277
|
+
if (observation?.attempt != null) observation.attempt.reason = observation.attempt.checks.length === 0 ? { type: "noSignature" } : {
|
|
1278
|
+
type: "uncoveredAttribution",
|
|
1279
|
+
attributionIds: [...attributions].map((id) => new URL(id))
|
|
1280
|
+
};
|
|
1281
|
+
logger.debug("Some attributions are not authenticated by the proofs: {attributions}.", { attributions: [...attributions] });
|
|
1282
|
+
return null;
|
|
1283
|
+
}
|
|
1284
|
+
return object;
|
|
1285
|
+
}
|
|
1286
|
+
function deleteAuthenticatedAttribution(attributions, controllerId, verificationMethodId) {
|
|
1287
|
+
if (!hasCryptographicOrigin(controllerId.href) && !isPortableId(controllerId)) {
|
|
1288
|
+
attributions.delete(controllerId.href);
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
const did = getPortableDid(controllerId);
|
|
1292
|
+
if (did == null || !hasCryptographicOrigin(verificationMethodId.href) || getPortableDid(verificationMethodId) !== did) return;
|
|
1293
|
+
for (const attribution of [...attributions]) if ((hasCryptographicOrigin(attribution) || isPortableId(attribution)) && getPortableDid(attribution) === did) attributions.delete(attribution);
|
|
1294
|
+
}
|
|
1295
|
+
function hasCryptographicOrigin(iri) {
|
|
1296
|
+
return /^did:/i.test(iri) || /^ap(?:\+ef61)?:\/\//i.test(iri);
|
|
1297
|
+
}
|
|
1298
|
+
//#endregion
|
|
1299
|
+
export { signObject as a, verifyPortableObjectProof as c, verifyProof as d, hasProofLike as i, verifyPortableObjectProofPolicy as l, classifyFep2277CoreType as n, verifyMapLocalProof as o, createProof as r, verifyObject as s, InvalidPortableObjectIdError as t, verifyPortableObjectProofWithRoot as u };
|