@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,833 @@
|
|
|
1
|
+
import "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
|
|
5
|
+
import "../std__assert-DvMDRPes.mjs";
|
|
6
|
+
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
7
|
+
import { l as verifyPortableObjectProofPolicy, o as verifyMapLocalProof } from "../proof-Dxy6ByqI.mjs";
|
|
8
|
+
import { n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
|
|
9
|
+
import { a as inspectCompoundPortableObjectApplicability, o as verifyCompoundPortableObjectProofs, s as verifyCompoundProofDocuments, t as containsCompoundPortableObject } from "../compound-proof-B-9mXxFW.mjs";
|
|
10
|
+
import { t as map_local_create_note_default } from "../map-local-create-note-B0SeXJms.mjs";
|
|
11
|
+
import { t as map_local_context_conflict_default } from "../map-local-context-conflict-DOYEEJ3u.mjs";
|
|
12
|
+
import { CryptographicKey, Multikey, Person } from "@fedify/vocab";
|
|
13
|
+
import { encodeMultibase, exportDidKey, parseIri } from "@fedify/vocab-runtime";
|
|
14
|
+
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
15
|
+
import serialize from "json-canon";
|
|
16
|
+
//#region src/sig/compound-proof-verification.test.ts
|
|
17
|
+
const limits = {
|
|
18
|
+
maxDepth: 16,
|
|
19
|
+
maxMaps: 32,
|
|
20
|
+
maxProofs: 8,
|
|
21
|
+
maxBytes: 16384
|
|
22
|
+
};
|
|
23
|
+
const options = {
|
|
24
|
+
contextLoader: mockDocumentLoader,
|
|
25
|
+
documentLoader() {
|
|
26
|
+
throw new TypeError("did:key must not use the document loader");
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
function createInlineProofContext() {
|
|
30
|
+
return {
|
|
31
|
+
id: "@id",
|
|
32
|
+
type: "@type",
|
|
33
|
+
Note: "https://www.w3.org/ns/activitystreams#Note",
|
|
34
|
+
attributedTo: {
|
|
35
|
+
"@id": "https://www.w3.org/ns/activitystreams#attributedTo",
|
|
36
|
+
"@type": "@id"
|
|
37
|
+
},
|
|
38
|
+
content: "https://www.w3.org/ns/activitystreams#content",
|
|
39
|
+
DataIntegrityProof: "https://w3id.org/security#DataIntegrityProof",
|
|
40
|
+
cryptosuite: "https://w3id.org/security#cryptosuite",
|
|
41
|
+
verificationMethod: {
|
|
42
|
+
"@id": "https://w3id.org/security#verificationMethod",
|
|
43
|
+
"@type": "@id"
|
|
44
|
+
},
|
|
45
|
+
proofPurpose: {
|
|
46
|
+
"@id": "https://w3id.org/security#proofPurpose",
|
|
47
|
+
"@type": "@vocab"
|
|
48
|
+
},
|
|
49
|
+
assertionMethod: "https://w3id.org/security#assertionMethod",
|
|
50
|
+
created: {
|
|
51
|
+
"@id": "http://purl.org/dc/terms/created",
|
|
52
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
53
|
+
},
|
|
54
|
+
proofValue: "https://w3id.org/security#proofValue",
|
|
55
|
+
proof: "https://w3id.org/security#proof"
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function asRecord(value) {
|
|
59
|
+
assert(value != null && typeof value === "object" && !Array.isArray(value));
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
async function sha256(value) {
|
|
63
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value)));
|
|
64
|
+
}
|
|
65
|
+
async function secureRawDocument(unsecuredDocument, privateJwk, verificationMethod, proofContext = unsecuredDocument["@context"]) {
|
|
66
|
+
const proofConfiguration = {
|
|
67
|
+
"@context": structuredClone(proofContext),
|
|
68
|
+
type: "DataIntegrityProof",
|
|
69
|
+
cryptosuite: "eddsa-jcs-2022",
|
|
70
|
+
verificationMethod,
|
|
71
|
+
proofPurpose: "assertionMethod",
|
|
72
|
+
created: "2023-02-24T23:36:38Z"
|
|
73
|
+
};
|
|
74
|
+
const proofDigest = await sha256(serialize(proofConfiguration));
|
|
75
|
+
const documentDigest = await sha256(serialize(unsecuredDocument));
|
|
76
|
+
const input = new Uint8Array(proofDigest.length + documentDigest.length);
|
|
77
|
+
input.set(proofDigest);
|
|
78
|
+
input.set(documentDigest, proofDigest.length);
|
|
79
|
+
const privateKey = await crypto.subtle.importKey("jwk", privateJwk, "Ed25519", false, ["sign"]);
|
|
80
|
+
const signature = new Uint8Array(await crypto.subtle.sign("Ed25519", privateKey, input));
|
|
81
|
+
return {
|
|
82
|
+
...unsecuredDocument,
|
|
83
|
+
proof: {
|
|
84
|
+
...proofConfiguration,
|
|
85
|
+
proofValue: new TextDecoder().decode(encodeMultibase("base58btc", signature))
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
test("verifyCompoundProofDocuments() verifies the recorded maps independently", async () => {
|
|
90
|
+
for (const fixture of [map_local_create_note_default, map_local_context_conflict_default]) {
|
|
91
|
+
const input = structuredClone(fixture.documents.finalSecuredCompound);
|
|
92
|
+
const result = await verifyCompoundProofDocuments(input, limits, options);
|
|
93
|
+
assertEquals(result.status, "ok");
|
|
94
|
+
if (result.status !== "ok") continue;
|
|
95
|
+
assert(result.verified);
|
|
96
|
+
assertEquals(result.documents.map(({ path, verified }) => ({
|
|
97
|
+
path,
|
|
98
|
+
verified
|
|
99
|
+
})), [{
|
|
100
|
+
path: "/object",
|
|
101
|
+
verified: true
|
|
102
|
+
}, {
|
|
103
|
+
path: "",
|
|
104
|
+
verified: true
|
|
105
|
+
}]);
|
|
106
|
+
assertEquals(result.documents.map((document) => document.verified ? document.key.id?.href : null), [fixture.keys.inner.verificationMethod, fixture.keys.outer.verificationMethod]);
|
|
107
|
+
assertEquals(result.snapshot, input);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
test("verifyCompoundProofDocuments() does not verify an empty proof set", async () => {
|
|
111
|
+
const result = await verifyCompoundProofDocuments({
|
|
112
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
113
|
+
id: "https://example.com/notes/unsigned",
|
|
114
|
+
type: "Note"
|
|
115
|
+
}, limits, options);
|
|
116
|
+
assertEquals(result.status, "ok");
|
|
117
|
+
if (result.status !== "ok") return;
|
|
118
|
+
assertEquals(result.verified, false);
|
|
119
|
+
assertEquals(result.documents, []);
|
|
120
|
+
assertEquals(result.statistics.proofCount, 0);
|
|
121
|
+
});
|
|
122
|
+
test("verifyCompoundProofDocuments() reports tampering and proof replacement per map", async () => {
|
|
123
|
+
const tampered = structuredClone(map_local_create_note_default.documents.finalSecuredCompound);
|
|
124
|
+
asRecord(tampered.object).content = "Tampered after both proofs";
|
|
125
|
+
const tamperedResult = await verifyCompoundProofDocuments(tampered, limits, options);
|
|
126
|
+
assertEquals(tamperedResult.status, "ok");
|
|
127
|
+
if (tamperedResult.status === "ok") {
|
|
128
|
+
assertEquals(tamperedResult.verified, false);
|
|
129
|
+
assertEquals(tamperedResult.documents.map(({ path, verified }) => ({
|
|
130
|
+
path,
|
|
131
|
+
verified
|
|
132
|
+
})), [{
|
|
133
|
+
path: "/object",
|
|
134
|
+
verified: false
|
|
135
|
+
}, {
|
|
136
|
+
path: "",
|
|
137
|
+
verified: false
|
|
138
|
+
}]);
|
|
139
|
+
}
|
|
140
|
+
const replaced = structuredClone(map_local_create_note_default.documents.finalSecuredCompound);
|
|
141
|
+
asRecord(replaced.object).proof = structuredClone(map_local_create_note_default.documents.replacementInnerProof);
|
|
142
|
+
const replacedResult = await verifyCompoundProofDocuments(replaced, limits, options);
|
|
143
|
+
assertEquals(replacedResult.status, "ok");
|
|
144
|
+
if (replacedResult.status === "ok") {
|
|
145
|
+
assertEquals(replacedResult.verified, false);
|
|
146
|
+
assertEquals(replacedResult.documents.map(({ path, verified }) => ({
|
|
147
|
+
path,
|
|
148
|
+
verified
|
|
149
|
+
})), [{
|
|
150
|
+
path: "/object",
|
|
151
|
+
verified: true
|
|
152
|
+
}, {
|
|
153
|
+
path: "",
|
|
154
|
+
verified: false
|
|
155
|
+
}]);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
test("verifyCompoundProofDocuments() requires a local context on nested secured maps", async () => {
|
|
159
|
+
const input = structuredClone(map_local_create_note_default.documents.finalSecuredCompound);
|
|
160
|
+
delete asRecord(input.object)["@context"];
|
|
161
|
+
const result = await verifyCompoundProofDocuments(input, limits, options);
|
|
162
|
+
assertEquals(result.status, "ok");
|
|
163
|
+
if (result.status !== "ok") return;
|
|
164
|
+
assertEquals(result.verified, false);
|
|
165
|
+
assertEquals(result.documents[0], {
|
|
166
|
+
path: "/object",
|
|
167
|
+
id: map_local_create_note_default.documents.securedInner.id,
|
|
168
|
+
depth: 1,
|
|
169
|
+
verified: false,
|
|
170
|
+
reason: { type: "missingContext" }
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
test("verifyCompoundProofDocuments() preserves proof aliases in the JCS input", async () => {
|
|
174
|
+
const secured = await secureRawDocument({
|
|
175
|
+
"@context": [
|
|
176
|
+
"https://www.w3.org/ns/activitystreams",
|
|
177
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
178
|
+
{ integrityProof: "https://w3id.org/security#proof" }
|
|
179
|
+
],
|
|
180
|
+
id: "https://example.com/notes/alias-bound",
|
|
181
|
+
type: "Note",
|
|
182
|
+
content: "Alias-bound content",
|
|
183
|
+
integrityProof: "This value is signed data, not the direct proof."
|
|
184
|
+
}, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod);
|
|
185
|
+
const result = await verifyCompoundProofDocuments(secured, limits, options);
|
|
186
|
+
assertEquals(result.status, "ok");
|
|
187
|
+
if (result.status !== "ok") return;
|
|
188
|
+
assert(result.verified);
|
|
189
|
+
const tampered = structuredClone(secured);
|
|
190
|
+
tampered.integrityProof = "Changed after signing";
|
|
191
|
+
const tamperedResult = await verifyCompoundProofDocuments(tampered, limits, options);
|
|
192
|
+
assertEquals(tamperedResult.status, "ok");
|
|
193
|
+
if (tamperedResult.status === "ok") assertEquals(tamperedResult.verified, false);
|
|
194
|
+
});
|
|
195
|
+
test("verifyCompoundProofDocuments() preserves the received document context", async () => {
|
|
196
|
+
const proofContext = ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"];
|
|
197
|
+
const result = await verifyCompoundProofDocuments(await secureRawDocument({
|
|
198
|
+
"@context": [...proofContext, { ex: "https://example.com/ns#" }],
|
|
199
|
+
id: "https://example.com/notes/distinct-contexts",
|
|
200
|
+
type: "Note",
|
|
201
|
+
content: "The document context is part of the signed input."
|
|
202
|
+
}, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod, proofContext), limits, options);
|
|
203
|
+
assertEquals(result.status, "ok");
|
|
204
|
+
if (result.status !== "ok") return;
|
|
205
|
+
assert(result.verified);
|
|
206
|
+
});
|
|
207
|
+
test("verifyCompoundProofDocuments() authenticates only received JSON values", async () => {
|
|
208
|
+
const secured = await secureRawDocument({
|
|
209
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
210
|
+
id: "https://example.com/notes/attachment-array",
|
|
211
|
+
type: "Note",
|
|
212
|
+
attachment: ["https://example.com/images/1"]
|
|
213
|
+
}, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod);
|
|
214
|
+
const verified = await verifyCompoundProofDocuments(secured, limits, options);
|
|
215
|
+
assertEquals(verified.status, "ok");
|
|
216
|
+
if (verified.status !== "ok") return;
|
|
217
|
+
assert(verified.verified);
|
|
218
|
+
const tampered = structuredClone(secured);
|
|
219
|
+
tampered.attachment = "https://example.com/images/1";
|
|
220
|
+
const result = await verifyCompoundProofDocuments(tampered, limits, options);
|
|
221
|
+
assertEquals(result.status, "ok");
|
|
222
|
+
if (result.status === "ok") assertEquals(result.verified, false);
|
|
223
|
+
});
|
|
224
|
+
test("verifyCompoundProofDocuments() contains malformed contexts per map", async () => {
|
|
225
|
+
const valid = await secureRawDocument({
|
|
226
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
227
|
+
id: "https://example.com/notes/valid-sibling",
|
|
228
|
+
type: "Note",
|
|
229
|
+
content: "This sibling must still produce a result."
|
|
230
|
+
}, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod);
|
|
231
|
+
const malformed = structuredClone(valid);
|
|
232
|
+
malformed["@context"] = 42;
|
|
233
|
+
malformed.id = "https://example.com/notes/malformed-context";
|
|
234
|
+
malformed.proof = {
|
|
235
|
+
"@context": [],
|
|
236
|
+
"@type": ["https://w3id.org/security#DataIntegrityProof"],
|
|
237
|
+
"https://w3id.org/security#cryptosuite": [{ "@value": "eddsa-jcs-2022" }],
|
|
238
|
+
"https://w3id.org/security#verificationMethod": [{ "@id": map_local_create_note_default.keys.outer.verificationMethod }],
|
|
239
|
+
"https://w3id.org/security#proofPurpose": [{ "@id": "https://w3id.org/security#assertionMethod" }],
|
|
240
|
+
"http://purl.org/dc/terms/created": [{
|
|
241
|
+
"@value": "2023-02-24T23:36:38Z",
|
|
242
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
243
|
+
}],
|
|
244
|
+
"https://w3id.org/security#proofValue": [{ "@value": asRecord(valid.proof).proofValue }]
|
|
245
|
+
};
|
|
246
|
+
const result = await verifyCompoundProofDocuments({
|
|
247
|
+
malformed,
|
|
248
|
+
valid
|
|
249
|
+
}, limits, options);
|
|
250
|
+
assertEquals(result.status, "ok");
|
|
251
|
+
if (result.status !== "ok") return;
|
|
252
|
+
assertEquals(result.verified, false);
|
|
253
|
+
assertEquals(result.documents.map((document) => ({
|
|
254
|
+
path: document.path,
|
|
255
|
+
verified: document.verified,
|
|
256
|
+
reason: document.verified ? void 0 : document.reason.type
|
|
257
|
+
})), [{
|
|
258
|
+
path: "/malformed",
|
|
259
|
+
verified: false,
|
|
260
|
+
reason: "invalidProof"
|
|
261
|
+
}, {
|
|
262
|
+
path: "/valid",
|
|
263
|
+
verified: true,
|
|
264
|
+
reason: void 0
|
|
265
|
+
}]);
|
|
266
|
+
});
|
|
267
|
+
test("verifyCompoundProofDocuments() does not fetch proof contexts", async () => {
|
|
268
|
+
let contextLoads = 0;
|
|
269
|
+
const result = await verifyCompoundProofDocuments({
|
|
270
|
+
"@context": "https://attacker.example/context",
|
|
271
|
+
id: "https://example.com/notes/remote-context",
|
|
272
|
+
type: "Note",
|
|
273
|
+
proof: {
|
|
274
|
+
type: "DataIntegrityProof",
|
|
275
|
+
cryptosuite: "eddsa-jcs-2022",
|
|
276
|
+
verificationMethod: map_local_create_note_default.keys.outer.verificationMethod,
|
|
277
|
+
proofPurpose: "assertionMethod",
|
|
278
|
+
created: "2023-02-24T23:36:38Z",
|
|
279
|
+
proofValue: map_local_create_note_default.proofValues.outer
|
|
280
|
+
}
|
|
281
|
+
}, limits, {
|
|
282
|
+
...options,
|
|
283
|
+
contextLoader() {
|
|
284
|
+
contextLoads++;
|
|
285
|
+
throw new TypeError("unexpected context fetch");
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
assertEquals(result.status, "ok");
|
|
289
|
+
if (result.status === "ok") assertEquals(result.verified, false);
|
|
290
|
+
assertEquals(contextLoads, 0);
|
|
291
|
+
});
|
|
292
|
+
test("verifyCompoundPortableObjectProofs() applies policy to every portable map", async () => {
|
|
293
|
+
let contextLoads = 0;
|
|
294
|
+
const result = await verifyCompoundPortableObjectProofs(map_local_create_note_default.documents.finalSecuredCompound, limits, {
|
|
295
|
+
...options,
|
|
296
|
+
contextLoader() {
|
|
297
|
+
contextLoads++;
|
|
298
|
+
throw new TypeError("unexpected context fetch");
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
assertEquals(result.status, "ok");
|
|
302
|
+
if (result.status !== "ok") return;
|
|
303
|
+
assert(result.verified);
|
|
304
|
+
assertEquals(contextLoads, 0);
|
|
305
|
+
assertEquals(result.portableObjects.map((document) => ({
|
|
306
|
+
path: document.path,
|
|
307
|
+
verified: document.verified,
|
|
308
|
+
keys: document.verified ? document.keys.map((key) => key.id?.href) : []
|
|
309
|
+
})), [{
|
|
310
|
+
path: "/object",
|
|
311
|
+
verified: true,
|
|
312
|
+
keys: [map_local_create_note_default.keys.inner.verificationMethod]
|
|
313
|
+
}, {
|
|
314
|
+
path: "",
|
|
315
|
+
verified: true,
|
|
316
|
+
keys: [map_local_create_note_default.keys.outer.verificationMethod]
|
|
317
|
+
}]);
|
|
318
|
+
});
|
|
319
|
+
test("verifyCompoundPortableObjectProofs() reports unsigned portable maps", async () => {
|
|
320
|
+
const context = ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"];
|
|
321
|
+
const result = await verifyCompoundPortableObjectProofs({
|
|
322
|
+
"@context": context,
|
|
323
|
+
id: map_local_create_note_default.documents.outerUnsecuredDocument.id,
|
|
324
|
+
type: "Create",
|
|
325
|
+
actor: map_local_create_note_default.documents.outerUnsecuredDocument.actor,
|
|
326
|
+
object: {
|
|
327
|
+
"@context": structuredClone(context),
|
|
328
|
+
id: map_local_create_note_default.documents.innerUnsecuredDocument.id,
|
|
329
|
+
type: "Note",
|
|
330
|
+
attributedTo: map_local_create_note_default.documents.innerUnsecuredDocument.attributedTo,
|
|
331
|
+
content: "Unsigned portable child"
|
|
332
|
+
}
|
|
333
|
+
}, limits, options);
|
|
334
|
+
assertEquals(result.status, "ok");
|
|
335
|
+
if (result.status !== "ok") return;
|
|
336
|
+
assertEquals(result.verified, false);
|
|
337
|
+
assertEquals(result.proofs, []);
|
|
338
|
+
assertEquals(result.portableObjects.map((document) => ({
|
|
339
|
+
path: document.path,
|
|
340
|
+
verified: document.verified,
|
|
341
|
+
reason: document.verified ? void 0 : document.reason.type
|
|
342
|
+
})), [{
|
|
343
|
+
path: "/object",
|
|
344
|
+
verified: false,
|
|
345
|
+
reason: "missingProof"
|
|
346
|
+
}, {
|
|
347
|
+
path: "",
|
|
348
|
+
verified: false,
|
|
349
|
+
reason: "missingProof"
|
|
350
|
+
}]);
|
|
351
|
+
});
|
|
352
|
+
test("verifyCompoundPortableObjectProofs() reports policy before crypto failure", async () => {
|
|
353
|
+
const mismatched = structuredClone(map_local_create_note_default.documents.finalSecuredCompound);
|
|
354
|
+
asRecord(mismatched.object).id = map_local_create_note_default.documents.outerUnsecuredDocument.id;
|
|
355
|
+
const result = await verifyCompoundPortableObjectProofs(mismatched, limits, options);
|
|
356
|
+
assertEquals(result.status, "ok");
|
|
357
|
+
if (result.status !== "ok") return;
|
|
358
|
+
assertEquals(result.verified, false);
|
|
359
|
+
const inner = result.portableObjects.find(({ path }) => path === "/object");
|
|
360
|
+
assert(inner != null && !inner.verified);
|
|
361
|
+
assertEquals(inner.reason.type, "verificationMethodMismatch");
|
|
362
|
+
});
|
|
363
|
+
test("verifyCompoundPortableObjectProofs() finds unsigned portable maps in arrays", async () => {
|
|
364
|
+
const result = await verifyCompoundPortableObjectProofs({
|
|
365
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
366
|
+
id: "https://social.example/activities/1",
|
|
367
|
+
type: "Create",
|
|
368
|
+
attachment: [{
|
|
369
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
370
|
+
id: map_local_create_note_default.documents.innerUnsecuredDocument.id,
|
|
371
|
+
type: "Note",
|
|
372
|
+
content: "Unsigned portable attachment"
|
|
373
|
+
}]
|
|
374
|
+
}, limits, options);
|
|
375
|
+
assertEquals(result.status, "ok");
|
|
376
|
+
if (result.status !== "ok") return;
|
|
377
|
+
assertEquals(result.verified, false);
|
|
378
|
+
assertEquals(result.portableObjects.map((document) => ({
|
|
379
|
+
path: document.path,
|
|
380
|
+
reason: document.verified ? void 0 : document.reason.type
|
|
381
|
+
})), [{
|
|
382
|
+
path: "/attachment/0",
|
|
383
|
+
reason: "missingProof"
|
|
384
|
+
}]);
|
|
385
|
+
});
|
|
386
|
+
test("verifyCompoundPortableObjectProofs() ignores context definitions", async () => {
|
|
387
|
+
const result = await verifyCompoundPortableObjectProofs({
|
|
388
|
+
"@context": { portable: {
|
|
389
|
+
"@id": map_local_create_note_default.documents.innerUnsecuredDocument.id,
|
|
390
|
+
"@context": { proof: "https://w3id.org/security#proof" }
|
|
391
|
+
} },
|
|
392
|
+
id: "https://social.example/objects/1",
|
|
393
|
+
type: "Note"
|
|
394
|
+
}, limits, options);
|
|
395
|
+
assertEquals(result.status, "ok");
|
|
396
|
+
if (result.status !== "ok") return;
|
|
397
|
+
assertEquals(result.verified, false);
|
|
398
|
+
assertEquals(result.proofs, []);
|
|
399
|
+
assertEquals(result.portableObjects, []);
|
|
400
|
+
});
|
|
401
|
+
test("verifyCompoundPortableObjectProofs() contains malformed portable IDs", async () => {
|
|
402
|
+
const result = await verifyCompoundPortableObjectProofs({
|
|
403
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
404
|
+
id: "ap://did%ZZkey/objects/1",
|
|
405
|
+
type: "Note"
|
|
406
|
+
}, limits, options);
|
|
407
|
+
assertEquals(result.status, "ok");
|
|
408
|
+
if (result.status !== "ok") return;
|
|
409
|
+
assertEquals(result.verified, false);
|
|
410
|
+
assertEquals(result.portableObjects.length, 1);
|
|
411
|
+
const [object] = result.portableObjects;
|
|
412
|
+
assert(!object.verified);
|
|
413
|
+
assertEquals(object.reason, { type: "invalidPortableObject" });
|
|
414
|
+
});
|
|
415
|
+
test("verifyPortableObjectProofPolicy() binds policy to the verified key", async () => {
|
|
416
|
+
const outerKey = await verifyMapLocalProof(map_local_create_note_default.documents.finalSecuredCompound, options);
|
|
417
|
+
assert(outerKey != null);
|
|
418
|
+
assertEquals(await verifyPortableObjectProofPolicy(map_local_create_note_default.documents.securedInner, outerKey, options), {
|
|
419
|
+
verified: false,
|
|
420
|
+
reason: {
|
|
421
|
+
type: "invalidProof",
|
|
422
|
+
proofIndex: 0
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
test("verifyPortableObjectProofPolicy() binds policy to the verified proof", async () => {
|
|
427
|
+
const context = createInlineProofContext();
|
|
428
|
+
context.proof = {
|
|
429
|
+
"@id": "https://w3id.org/security#proof",
|
|
430
|
+
"@context": { assertionMethod: "https://w3id.org/security#authentication" }
|
|
431
|
+
};
|
|
432
|
+
const secured = await secureRawDocument({
|
|
433
|
+
"@context": context,
|
|
434
|
+
id: map_local_create_note_default.documents.innerUnsecuredDocument.id,
|
|
435
|
+
type: "Note",
|
|
436
|
+
attributedTo: map_local_create_note_default.documents.innerUnsecuredDocument.attributedTo,
|
|
437
|
+
content: "Property-scoped proof purpose"
|
|
438
|
+
}, map_local_create_note_default.keys.inner.testPrivateKeyJwk, map_local_create_note_default.keys.inner.verificationMethod);
|
|
439
|
+
delete asRecord(secured.proof)["@context"];
|
|
440
|
+
const key = await verifyMapLocalProof(secured, options);
|
|
441
|
+
assert(key != null);
|
|
442
|
+
assertEquals(await verifyPortableObjectProofPolicy(secured, key, options), {
|
|
443
|
+
verified: false,
|
|
444
|
+
reason: {
|
|
445
|
+
type: "invalidProof",
|
|
446
|
+
proofIndex: 0
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
test("verifyCompoundPortableObjectProofs() reports the policy-validated ID", async () => {
|
|
451
|
+
const context = createInlineProofContext();
|
|
452
|
+
context.id = "https://example.com/id";
|
|
453
|
+
const result = await verifyCompoundPortableObjectProofs(await secureRawDocument({
|
|
454
|
+
"@context": context,
|
|
455
|
+
id: map_local_create_note_default.documents.outerUnsecuredDocument.id,
|
|
456
|
+
"@id": map_local_create_note_default.documents.innerUnsecuredDocument.id,
|
|
457
|
+
type: "Note",
|
|
458
|
+
content: "Conflicting raw ID"
|
|
459
|
+
}, map_local_create_note_default.keys.inner.testPrivateKeyJwk, map_local_create_note_default.keys.inner.verificationMethod), limits, options);
|
|
460
|
+
assertEquals(result.status, "ok");
|
|
461
|
+
if (result.status !== "ok") return;
|
|
462
|
+
assert(result.verified);
|
|
463
|
+
assertEquals(result.portableObjects.length, 1);
|
|
464
|
+
assertEquals(result.portableObjects[0].id, map_local_create_note_default.documents.innerUnsecuredDocument.id);
|
|
465
|
+
});
|
|
466
|
+
const compatibleDid = await exportDidKey(ed25519PublicKey.publicKey);
|
|
467
|
+
const compatibleVerificationMethod = `${compatibleDid}#${compatibleDid.slice(8)}`;
|
|
468
|
+
const compatibleActorId = `https://gw.example/.well-known/apgateway/${compatibleDid}/actor`;
|
|
469
|
+
const compatibleContext = ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"];
|
|
470
|
+
async function signCompatible(document) {
|
|
471
|
+
return await secureRawDocument(document, await crypto.subtle.exportKey("jwk", ed25519PrivateKey), compatibleVerificationMethod);
|
|
472
|
+
}
|
|
473
|
+
async function compatibleActor(id = compatibleActorId, keyBase = compatibleActorId) {
|
|
474
|
+
const actorId = parseIri(id);
|
|
475
|
+
return await new Person({
|
|
476
|
+
id: actorId,
|
|
477
|
+
inbox: new URL(`${keyBase}/inbox`),
|
|
478
|
+
outbox: new URL(`${keyBase}/outbox`),
|
|
479
|
+
gateways: [new URL("https://gw.example")],
|
|
480
|
+
publicKey: new CryptographicKey({
|
|
481
|
+
id: new URL(`${keyBase}#main-key`),
|
|
482
|
+
owner: actorId,
|
|
483
|
+
publicKey: rsaPublicKey2.publicKey
|
|
484
|
+
}),
|
|
485
|
+
assertionMethods: [new Multikey({
|
|
486
|
+
id: new URL(`${keyBase}#ed25519-key`),
|
|
487
|
+
controller: actorId,
|
|
488
|
+
publicKey: ed25519PublicKey.publicKey
|
|
489
|
+
})]
|
|
490
|
+
}).toJsonLd({
|
|
491
|
+
format: "compact",
|
|
492
|
+
contextLoader: mockDocumentLoader
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
test("verifyCompoundPortableObjectProofs() treats compatible-ID maps as portable", async () => {
|
|
496
|
+
const note = {
|
|
497
|
+
"@context": [...compatibleContext],
|
|
498
|
+
id: `${compatibleActorId}/notes/1`,
|
|
499
|
+
type: "Note",
|
|
500
|
+
attributedTo: compatibleActorId,
|
|
501
|
+
content: "Hello"
|
|
502
|
+
};
|
|
503
|
+
const ordinary = {
|
|
504
|
+
"@context": [...compatibleContext],
|
|
505
|
+
id: "https://social.example/activities/1",
|
|
506
|
+
type: "Announce",
|
|
507
|
+
actor: "https://social.example/users/bob",
|
|
508
|
+
object: note
|
|
509
|
+
};
|
|
510
|
+
assertEquals(inspectCompoundPortableObjectApplicability(ordinary, limits), "present");
|
|
511
|
+
assert(containsCompoundPortableObject(ordinary));
|
|
512
|
+
const result = await verifyCompoundPortableObjectProofs(ordinary, limits, options);
|
|
513
|
+
assert(result.status === "ok");
|
|
514
|
+
assertEquals(result.verified, false);
|
|
515
|
+
assertEquals(result.portableObjects.map((o) => [o.path, o.verified]), [["/object", false]]);
|
|
516
|
+
const signed = await verifyCompoundPortableObjectProofs({
|
|
517
|
+
...ordinary,
|
|
518
|
+
object: await signCompatible(note)
|
|
519
|
+
}, limits, options);
|
|
520
|
+
assert(signed.status === "ok");
|
|
521
|
+
assert(signed.verified);
|
|
522
|
+
});
|
|
523
|
+
test("verifyCompoundPortableObjectProofs() checks compatible activity IDs", async () => {
|
|
524
|
+
const create = (did) => ({
|
|
525
|
+
"@context": [...compatibleContext],
|
|
526
|
+
id: `https://gw.example/.well-known/apgateway/${did}/activities/1`,
|
|
527
|
+
type: "Create",
|
|
528
|
+
actor: compatibleActorId,
|
|
529
|
+
object: "https://social.example/notes/1"
|
|
530
|
+
});
|
|
531
|
+
const own = await verifyCompoundPortableObjectProofs(await signCompatible(create(compatibleDid)), limits, options);
|
|
532
|
+
assert(own.status === "ok");
|
|
533
|
+
assert(own.verified);
|
|
534
|
+
const forged = await verifyCompoundPortableObjectProofs(await signCompatible(create("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK")), limits, options);
|
|
535
|
+
assert(forged.status === "ok");
|
|
536
|
+
assertEquals(forged.verified, false);
|
|
537
|
+
const [object] = forged.portableObjects;
|
|
538
|
+
assert(!object.verified);
|
|
539
|
+
assertEquals(object.reason.type, "verificationMethodMismatch");
|
|
540
|
+
});
|
|
541
|
+
test("verifyCompoundPortableObjectProofs() exempts keys embedded in portable actors", async () => {
|
|
542
|
+
let result = await verifyCompoundPortableObjectProofs(await signCompatible(await compatibleActor()), limits, options);
|
|
543
|
+
assert(result.status === "ok");
|
|
544
|
+
assert(result.verified);
|
|
545
|
+
assertEquals(result.portableObjects.map((o) => o.path), [""]);
|
|
546
|
+
result = await verifyCompoundPortableObjectProofs(await signCompatible(await compatibleActor(`ap://${compatibleDid}/actor`)), limits, options);
|
|
547
|
+
assert(result.status === "ok");
|
|
548
|
+
assert(result.verified);
|
|
549
|
+
result = await verifyCompoundPortableObjectProofs({
|
|
550
|
+
"@context": [...compatibleContext],
|
|
551
|
+
id: "https://social.example/activities/2",
|
|
552
|
+
type: "Update",
|
|
553
|
+
actor: "https://social.example/users/bob",
|
|
554
|
+
object: await signCompatible(await compatibleActor())
|
|
555
|
+
}, limits, options);
|
|
556
|
+
assert(result.status === "ok");
|
|
557
|
+
assert(result.verified);
|
|
558
|
+
});
|
|
559
|
+
test("verifyCompoundPortableObjectProofs() exempts only keys of the parent itself", async () => {
|
|
560
|
+
const actor = await compatibleActor();
|
|
561
|
+
const otherActorId = "https://gw.example/.well-known/apgateway/did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/actor";
|
|
562
|
+
const publicKey = actor.publicKey;
|
|
563
|
+
const cases = {
|
|
564
|
+
"a key of another actor": {
|
|
565
|
+
...actor,
|
|
566
|
+
publicKey: {
|
|
567
|
+
...publicKey,
|
|
568
|
+
id: `${otherActorId}#main-key`,
|
|
569
|
+
owner: otherActorId
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
"a key owned by another actor": {
|
|
573
|
+
...actor,
|
|
574
|
+
publicKey: {
|
|
575
|
+
...publicKey,
|
|
576
|
+
owner: otherActorId
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
"a non-key object": {
|
|
580
|
+
...actor,
|
|
581
|
+
publicKey: {
|
|
582
|
+
id: `${compatibleActorId}#note`,
|
|
583
|
+
type: "Note",
|
|
584
|
+
content: "Not a key"
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
"a key with extra content": {
|
|
588
|
+
...actor,
|
|
589
|
+
publicKey: {
|
|
590
|
+
...publicKey,
|
|
591
|
+
name: "Extra"
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
"a key under another property": {
|
|
595
|
+
...actor,
|
|
596
|
+
attachment: structuredClone(publicKey)
|
|
597
|
+
},
|
|
598
|
+
"a parent context with a keyword alias": {
|
|
599
|
+
...actor,
|
|
600
|
+
"@context": [...actor["@context"], { kind: "@type" }]
|
|
601
|
+
},
|
|
602
|
+
"a key with a scoped context": {
|
|
603
|
+
...actor,
|
|
604
|
+
publicKey: {
|
|
605
|
+
...publicKey,
|
|
606
|
+
"@context": { CryptographicKey: {
|
|
607
|
+
"@id": "https://w3id.org/security#Key",
|
|
608
|
+
"@context": {}
|
|
609
|
+
} }
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
for (const [name, document] of Object.entries(cases)) {
|
|
614
|
+
const result = await verifyCompoundPortableObjectProofs(await signCompatible(document), limits, options);
|
|
615
|
+
assert(result.status === "ok", name);
|
|
616
|
+
assertEquals(result.verified, false, name);
|
|
617
|
+
assert(result.portableObjects.find((o) => o.path === "")?.verified, name);
|
|
618
|
+
assert(result.portableObjects.some((o) => o.path !== "" && !o.verified), name);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
/**
|
|
622
|
+
* Rewrites the IDs of a {@link compatibleActor}'s keys as they are written,
|
|
623
|
+
* e.g., as `ap:` URIs, which the serializer would canonicalize otherwise.
|
|
624
|
+
*/
|
|
625
|
+
function withKeyIds(actor, keyBase, { id = actor.id, owner = id } = {}) {
|
|
626
|
+
const [multikey] = actor.assertionMethod;
|
|
627
|
+
return {
|
|
628
|
+
...actor,
|
|
629
|
+
id,
|
|
630
|
+
publicKey: {
|
|
631
|
+
...asRecord(actor.publicKey),
|
|
632
|
+
id: `${keyBase}#main-key`,
|
|
633
|
+
owner
|
|
634
|
+
},
|
|
635
|
+
assertionMethod: [{
|
|
636
|
+
...multikey,
|
|
637
|
+
id: `${keyBase}#ed25519-key`,
|
|
638
|
+
controller: owner
|
|
639
|
+
}]
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
const apActorId = `ap://${compatibleDid}/actor`;
|
|
643
|
+
test("verifyCompoundPortableObjectProofs() exempts keys at ap: URIs embedded in portable actors", async () => {
|
|
644
|
+
const actor = await compatibleActor();
|
|
645
|
+
const encodedDid = encodeURIComponent(compatibleDid);
|
|
646
|
+
const cases = {
|
|
647
|
+
"ap: keys of an ap: actor": [apActorId, apActorId],
|
|
648
|
+
"ap: keys of a compatible-ID actor": [compatibleActorId, apActorId],
|
|
649
|
+
"ap+ef61: keys": [apActorId, `ap+ef61://${compatibleDid}/actor`],
|
|
650
|
+
"keys with percent-encoded DIDs": [apActorId, `ap://${encodedDid}/actor`],
|
|
651
|
+
"ap+ef61: keys of a percent-encoded actor": [`ap+ef61://${encodedDid}/actor`, `ap+ef61://${encodedDid}/actor`],
|
|
652
|
+
"keys with an uppercase scheme": [apActorId, `AP://${compatibleDid}/actor`],
|
|
653
|
+
"keys with location hints": [apActorId, `${apActorId}?@gateway=https%3A%2F%2Fgw.example`]
|
|
654
|
+
};
|
|
655
|
+
for (const [name, [id, keyBase]] of Object.entries(cases)) {
|
|
656
|
+
const result = await verifyCompoundPortableObjectProofs(await signCompatible(withKeyIds(actor, keyBase, { id })), limits, options);
|
|
657
|
+
assert(result.status === "ok", name);
|
|
658
|
+
assert(result.verified, name);
|
|
659
|
+
assertEquals(result.portableObjects.map((o) => o.path), [""], name);
|
|
660
|
+
}
|
|
661
|
+
const apActor = withKeyIds(actor, apActorId, { id: apActorId });
|
|
662
|
+
const atIdKeys = (key) => {
|
|
663
|
+
const { id, ...rest } = asRecord(key);
|
|
664
|
+
return {
|
|
665
|
+
...rest,
|
|
666
|
+
"@id": id
|
|
667
|
+
};
|
|
668
|
+
};
|
|
669
|
+
const withAtIds = await verifyCompoundPortableObjectProofs(await signCompatible({
|
|
670
|
+
...apActor,
|
|
671
|
+
publicKey: atIdKeys(apActor.publicKey),
|
|
672
|
+
assertionMethod: apActor.assertionMethod.map(atIdKeys)
|
|
673
|
+
}), limits, options);
|
|
674
|
+
assert(withAtIds.status === "ok");
|
|
675
|
+
assert(withAtIds.verified);
|
|
676
|
+
assertEquals(withAtIds.portableObjects.map((o) => o.path), [""]);
|
|
677
|
+
const withApTerm = await verifyCompoundPortableObjectProofs(await signCompatible({
|
|
678
|
+
...withKeyIds(actor, apActorId, { id: apActorId }),
|
|
679
|
+
"@context": [...actor["@context"], { ap: "https://ap.example/" }]
|
|
680
|
+
}), limits, options);
|
|
681
|
+
assert(withApTerm.status === "ok");
|
|
682
|
+
assert(withApTerm.verified);
|
|
683
|
+
assertEquals(withApTerm.portableObjects.map((o) => o.path), [""]);
|
|
684
|
+
const result = await verifyCompoundPortableObjectProofs(await signCompatible({
|
|
685
|
+
"@context": [...compatibleContext],
|
|
686
|
+
id: `ap://${compatibleDid}/activities/1`,
|
|
687
|
+
type: "Update",
|
|
688
|
+
actor: apActorId,
|
|
689
|
+
object: await signCompatible(withKeyIds(actor, apActorId, { id: apActorId }))
|
|
690
|
+
}), limits, options);
|
|
691
|
+
assert(result.status === "ok");
|
|
692
|
+
assert(result.verified);
|
|
693
|
+
assertEquals(result.portableObjects.map((o) => o.path), ["/object", ""]);
|
|
694
|
+
});
|
|
695
|
+
test("verifyCompoundPortableObjectProofs() exempts only ap: keys of the parent itself", async () => {
|
|
696
|
+
const actor = withKeyIds(await compatibleActor(), apActorId, { id: apActorId });
|
|
697
|
+
const publicKey = asRecord(actor.publicKey);
|
|
698
|
+
const otherDid = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
|
|
699
|
+
const keyIds = {
|
|
700
|
+
"a key of another actor": `ap://${compatibleDid}/other#main-key`,
|
|
701
|
+
"a key of another DID": `ap://${otherDid}/actor#main-key`,
|
|
702
|
+
"a key without a fragment": apActorId,
|
|
703
|
+
"a key with an empty fragment": `${apActorId}#`,
|
|
704
|
+
"a key with dot segments": `ap://${compatibleDid}/x/../actor#main-key`,
|
|
705
|
+
"a key with encoded dot segments": `ap://${compatibleDid}/x/%2E%2E/actor#main-key`,
|
|
706
|
+
"a key with an encoded slash": `${apActorId}%2F#main-key`,
|
|
707
|
+
"a key with an encoded fragment delimiter": `${apActorId}%23main-key`,
|
|
708
|
+
"a compatible key with dot segments": `https://gw.example/.well-known/apgateway/${compatibleDid}/x/../actor#main-key`
|
|
709
|
+
};
|
|
710
|
+
const cases = {};
|
|
711
|
+
for (const [name, id] of Object.entries(keyIds)) cases[name] = {
|
|
712
|
+
...actor,
|
|
713
|
+
publicKey: {
|
|
714
|
+
...publicKey,
|
|
715
|
+
id
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
Object.assign(cases, {
|
|
719
|
+
"a key owned by another actor": {
|
|
720
|
+
...actor,
|
|
721
|
+
publicKey: {
|
|
722
|
+
...publicKey,
|
|
723
|
+
owner: `ap://${otherDid}/actor`
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
"a key controlled by another actor": {
|
|
727
|
+
...actor,
|
|
728
|
+
assertionMethod: actor.assertionMethod.map((key) => ({
|
|
729
|
+
...key,
|
|
730
|
+
controller: `ap://${otherDid}/actor`
|
|
731
|
+
}))
|
|
732
|
+
},
|
|
733
|
+
"a key with extra content": {
|
|
734
|
+
...actor,
|
|
735
|
+
publicKey: {
|
|
736
|
+
...publicKey,
|
|
737
|
+
name: "Extra"
|
|
738
|
+
}
|
|
739
|
+
},
|
|
740
|
+
"a key with a scoped context": {
|
|
741
|
+
...actor,
|
|
742
|
+
publicKey: {
|
|
743
|
+
...publicKey,
|
|
744
|
+
"@context": { CryptographicKey: {
|
|
745
|
+
"@id": "https://w3id.org/security#Key",
|
|
746
|
+
"@context": {}
|
|
747
|
+
} }
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
for (const [name, document] of Object.entries(cases)) {
|
|
752
|
+
const result = await verifyCompoundPortableObjectProofs(await signCompatible(document), limits, options);
|
|
753
|
+
assert(result.status === "ok", name);
|
|
754
|
+
assertEquals(result.verified, false, name);
|
|
755
|
+
assert(result.portableObjects.find((o) => o.path === "")?.verified, name);
|
|
756
|
+
assert(result.portableObjects.some((o) => o.path !== "" && !o.verified), name);
|
|
757
|
+
}
|
|
758
|
+
const conflicting = await verifyCompoundPortableObjectProofs(await signCompatible({
|
|
759
|
+
...actor,
|
|
760
|
+
publicKey: {
|
|
761
|
+
...publicKey,
|
|
762
|
+
"@id": `ap://${compatibleDid}/other#main-key`
|
|
763
|
+
}
|
|
764
|
+
}), limits, options);
|
|
765
|
+
assert(conflicting.status === "ok");
|
|
766
|
+
assertEquals(conflicting.verified, false);
|
|
767
|
+
});
|
|
768
|
+
test("verifyCompoundPortableObjectProofs() exempts ap: keys only of verified actors", async () => {
|
|
769
|
+
const actor = withKeyIds(await compatibleActor(), apActorId, { id: apActorId });
|
|
770
|
+
const signed = await signCompatible(actor);
|
|
771
|
+
const unverified = {
|
|
772
|
+
"": true,
|
|
773
|
+
"/object": false,
|
|
774
|
+
"/object/publicKey": false,
|
|
775
|
+
"/object/assertionMethod/0": false
|
|
776
|
+
};
|
|
777
|
+
const cases = {
|
|
778
|
+
"an unsigned actor": [actor, unverified],
|
|
779
|
+
"a tampered actor": [{
|
|
780
|
+
...signed,
|
|
781
|
+
name: "Tampered"
|
|
782
|
+
}, unverified],
|
|
783
|
+
"an actor with a key with an invalid proof": [await signCompatible({
|
|
784
|
+
...actor,
|
|
785
|
+
publicKey: {
|
|
786
|
+
...asRecord(actor.publicKey),
|
|
787
|
+
"@context": [...compatibleContext],
|
|
788
|
+
proof: asRecord(signed.proof)
|
|
789
|
+
}
|
|
790
|
+
}), {
|
|
791
|
+
"": true,
|
|
792
|
+
"/object": true,
|
|
793
|
+
"/object/publicKey": false
|
|
794
|
+
}]
|
|
795
|
+
};
|
|
796
|
+
for (const [name, [object, expected]] of Object.entries(cases)) {
|
|
797
|
+
const result = await verifyCompoundPortableObjectProofs(await signCompatible({
|
|
798
|
+
"@context": [...compatibleContext],
|
|
799
|
+
id: `ap://${compatibleDid}/activities/1`,
|
|
800
|
+
type: "Update",
|
|
801
|
+
actor: apActorId,
|
|
802
|
+
object
|
|
803
|
+
}), limits, options);
|
|
804
|
+
assert(result.status === "ok", name);
|
|
805
|
+
assertEquals(result.verified, false, name);
|
|
806
|
+
assertEquals(Object.fromEntries(result.portableObjects.map((o) => [o.path, o.verified])), expected, name);
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
test("verifyCompoundPortableObjectProofs() leaves keys at DID URLs alone", async () => {
|
|
810
|
+
const result = await verifyCompoundPortableObjectProofs(await signCompatible(withKeyIds(await compatibleActor(), compatibleDid, { id: apActorId })), limits, options);
|
|
811
|
+
assert(result.status === "ok");
|
|
812
|
+
assert(result.verified);
|
|
813
|
+
assertEquals(result.portableObjects.map((o) => o.path), [""]);
|
|
814
|
+
});
|
|
815
|
+
test("compound proof observation does not throw for malformed subject IDs", async () => {
|
|
816
|
+
const { verificationObservation } = await import("../verification-C3954hQh.mjs");
|
|
817
|
+
const evidence = { attempts: [] };
|
|
818
|
+
const result = await verifyCompoundPortableObjectProofs({
|
|
819
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
820
|
+
id: "ap://did%ZZkey/objects/1",
|
|
821
|
+
type: "Note",
|
|
822
|
+
proof: {}
|
|
823
|
+
}, limits, {
|
|
824
|
+
...options,
|
|
825
|
+
[verificationObservation]: evidence
|
|
826
|
+
});
|
|
827
|
+
assertEquals(result.status, "ok");
|
|
828
|
+
assert(result.status === "ok" && !result.verified);
|
|
829
|
+
assertEquals(evidence.attempts[0].subject.id, null);
|
|
830
|
+
assertEquals(evidence.attempts[0].subject.pointer, "");
|
|
831
|
+
});
|
|
832
|
+
//#endregion
|
|
833
|
+
export {};
|