@fedify/fedify 2.4.0-dev.1964 → 2.4.0-dev.1995
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{builder-B4zpKIFU.mjs → builder-D0DxgOV5.mjs} +2 -2
- package/dist/{client-CtxvmazV.mjs → client-DpnpgjyX.mjs} +78 -4
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/compound-proof-CJnWc6c-.mjs +495 -0
- package/dist/{context-COLwHogJ.d.cts → context-BeDnDuF3.d.cts} +4 -3
- package/dist/{context-DiiCt4S7.d.ts → context-uyRUWoZQ.d.ts} +4 -3
- package/dist/{deno-CtG93ohj.mjs → deno-CABhzCb_.mjs} +1 -1
- package/dist/{docloader-dk_k3S8d.mjs → docloader-BbGSL0Xq.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler-compound-proof.test.d.mts +2 -0
- package/dist/federation/handler-compound-proof.test.mjs +152 -0
- package/dist/federation/handler.test.mjs +122 -4
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/keycache.test.mjs +52 -9
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +111 -15
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/send.test.mjs +139 -4
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-Drjlrrax.cjs → federation-DCEHQk4G.cjs} +632 -57
- package/dist/{http-6TI0tI03.d.cts → http-65PYi9jY.d.ts} +34 -2
- package/dist/{http-Dj4s1UQ0.js → http-9WyuldYh.js} +170 -8
- package/dist/{http-CdrSn2ip.d.ts → http-CP-7OdSq.d.cts} +34 -2
- package/dist/{http-B3skRvGo.cjs → http-CRFns-UE.cjs} +181 -7
- package/dist/{http-D7LX-BL9.mjs → http-D9urSO2D.mjs} +3 -3
- package/dist/{key-BVjgbs_9.mjs → key-Ccs8cnd7.mjs} +169 -7
- package/dist/{keycache-ZLqIfrkL.mjs → keycache-BUDHB5rY.mjs} +12 -4
- package/dist/{kv-cache-DtsjH_bY.cjs → kv-cache-DXT9vmIg.cjs} +2 -2
- package/dist/{kv-cache-tGNbvDCF.js → kv-cache-DtmXhPXG.js} +2 -2
- package/dist/{kv-cache-xRcC_xxP.mjs → kv-cache-lKyFn_Dn.mjs} +1 -1
- package/dist/{ld-OuCeQFny.mjs → ld-qup__MAW.mjs} +3 -3
- package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
- package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
- package/dist/{metrics-B9N27cCB.mjs → metrics-CISXCvB1.mjs} +1 -1
- package/dist/{middleware-TeLjzoxn.mjs → middleware-CzvJvPHK.mjs} +58 -15
- package/dist/{middleware-Cd_vPqDH.mjs → middleware-DkrU2WhY.mjs} +1 -1
- package/dist/{middleware-CPTDFMmJ.js → middleware-DsfWajaD.js} +593 -18
- package/dist/{mod-DypxPS9H.d.ts → mod-C0BYc0OU.d.ts} +1 -1
- package/dist/{mod-CnGLNoVw.d.cts → mod-CbKDOnCt.d.cts} +1 -1
- package/dist/{mod-CLHkCwLm.d.cts → mod-CyiSjRkk.d.cts} +1 -1
- package/dist/{mod-CSH1Ee-I.d.ts → mod-D882_Xsg.d.ts} +1 -1
- package/dist/{mod-DzRXACM5.d.ts → mod-UDwMjuHM.d.ts} +1 -1
- package/dist/{mod-BZldCEU0.d.cts → mod-x9W0d0Pk.d.cts} +1 -1
- package/dist/mod.cjs +20 -20
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +28 -1
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/{owner-DGSfntI-.d.ts → owner-BF0L9cMt.d.cts} +12 -1
- package/dist/{proof-BO_esQii.js → owner-BIPEZGiU.js} +1255 -1098
- package/dist/{owner-r_waTiQj.d.cts → owner-BSzr5-I8.d.ts} +12 -1
- package/dist/owner-CRoq8Spb.mjs +175 -0
- package/dist/{proof-Dua0d906.cjs → owner-CVA7L9MA.cjs} +1266 -1097
- package/dist/{proof-D5Ajk4DJ.mjs → proof-BDxNVZ3E.mjs} +186 -83
- package/dist/{send-DVXyUqhO.mjs → send-BzM5GLyn.mjs} +68 -11
- 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-verification.test.d.mts +2 -0
- package/dist/sig/compound-proof-verification.test.mjs +465 -0
- package/dist/sig/compound-proof.test.d.mts +2 -0
- package/dist/sig/compound-proof.test.mjs +324 -0
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +169 -2
- package/dist/sig/ld.test.mjs +55 -2
- package/dist/sig/mod.cjs +17 -17
- package/dist/sig/mod.d.cts +3 -3
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +103 -4
- package/dist/sig/proof.test.mjs +120 -1
- package/dist/{temporal-BoDw51A-.mjs → temporal-Dv6Qv-8k.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +2 -1
- package/dist/{types-RXm5YxpC.cjs → types-Be2zbyeG.cjs} +94 -2
- package/dist/{types-Dtyf8lVj.js → types-C_cNlNVK.js} +78 -4
- package/dist/utils/docloader.test.mjs +27 -3
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +6 -6
- package/dist/owner-DTEqTzSr.mjs +0 -120
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { t as assertEquals } from "../assert_equals-CBlRfA-_.mjs";
|
|
5
|
+
import "../std__assert-CxFT0MNt.mjs";
|
|
6
|
+
import { t as assert } from "../assert-CebeUf0C.mjs";
|
|
7
|
+
import { i as verifyMapLocalProof, s as verifyPortableObjectProofPolicy } from "../proof-BDxNVZ3E.mjs";
|
|
8
|
+
import { i as verifyCompoundProofDocuments, r as verifyCompoundPortableObjectProofs } from "../compound-proof-CJnWc6c-.mjs";
|
|
9
|
+
import { t as map_local_create_note_default } from "../map-local-create-note-B0SeXJms.mjs";
|
|
10
|
+
import { t as map_local_context_conflict_default } from "../map-local-context-conflict-DOYEEJ3u.mjs";
|
|
11
|
+
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
12
|
+
import { encodeMultibase } from "@fedify/vocab-runtime";
|
|
13
|
+
import serialize from "json-canon";
|
|
14
|
+
//#region src/sig/compound-proof-verification.test.ts
|
|
15
|
+
const limits = {
|
|
16
|
+
maxDepth: 16,
|
|
17
|
+
maxMaps: 32,
|
|
18
|
+
maxProofs: 8,
|
|
19
|
+
maxBytes: 16384
|
|
20
|
+
};
|
|
21
|
+
const options = {
|
|
22
|
+
contextLoader: mockDocumentLoader,
|
|
23
|
+
documentLoader() {
|
|
24
|
+
throw new TypeError("did:key must not use the document loader");
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function createInlineProofContext() {
|
|
28
|
+
return {
|
|
29
|
+
id: "@id",
|
|
30
|
+
type: "@type",
|
|
31
|
+
Note: "https://www.w3.org/ns/activitystreams#Note",
|
|
32
|
+
attributedTo: {
|
|
33
|
+
"@id": "https://www.w3.org/ns/activitystreams#attributedTo",
|
|
34
|
+
"@type": "@id"
|
|
35
|
+
},
|
|
36
|
+
content: "https://www.w3.org/ns/activitystreams#content",
|
|
37
|
+
DataIntegrityProof: "https://w3id.org/security#DataIntegrityProof",
|
|
38
|
+
cryptosuite: "https://w3id.org/security#cryptosuite",
|
|
39
|
+
verificationMethod: {
|
|
40
|
+
"@id": "https://w3id.org/security#verificationMethod",
|
|
41
|
+
"@type": "@id"
|
|
42
|
+
},
|
|
43
|
+
proofPurpose: {
|
|
44
|
+
"@id": "https://w3id.org/security#proofPurpose",
|
|
45
|
+
"@type": "@vocab"
|
|
46
|
+
},
|
|
47
|
+
assertionMethod: "https://w3id.org/security#assertionMethod",
|
|
48
|
+
created: {
|
|
49
|
+
"@id": "http://purl.org/dc/terms/created",
|
|
50
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
51
|
+
},
|
|
52
|
+
proofValue: "https://w3id.org/security#proofValue",
|
|
53
|
+
proof: "https://w3id.org/security#proof"
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function asRecord(value) {
|
|
57
|
+
assert(value != null && typeof value === "object" && !Array.isArray(value));
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
async function sha256(value) {
|
|
61
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value)));
|
|
62
|
+
}
|
|
63
|
+
async function secureRawDocument(unsecuredDocument, privateJwk, verificationMethod, proofContext = unsecuredDocument["@context"]) {
|
|
64
|
+
const proofConfiguration = {
|
|
65
|
+
"@context": structuredClone(proofContext),
|
|
66
|
+
type: "DataIntegrityProof",
|
|
67
|
+
cryptosuite: "eddsa-jcs-2022",
|
|
68
|
+
verificationMethod,
|
|
69
|
+
proofPurpose: "assertionMethod",
|
|
70
|
+
created: "2023-02-24T23:36:38Z"
|
|
71
|
+
};
|
|
72
|
+
const proofDigest = await sha256(serialize(proofConfiguration));
|
|
73
|
+
const documentDigest = await sha256(serialize(unsecuredDocument));
|
|
74
|
+
const input = new Uint8Array(proofDigest.length + documentDigest.length);
|
|
75
|
+
input.set(proofDigest);
|
|
76
|
+
input.set(documentDigest, proofDigest.length);
|
|
77
|
+
const privateKey = await crypto.subtle.importKey("jwk", privateJwk, "Ed25519", false, ["sign"]);
|
|
78
|
+
const signature = new Uint8Array(await crypto.subtle.sign("Ed25519", privateKey, input));
|
|
79
|
+
return {
|
|
80
|
+
...unsecuredDocument,
|
|
81
|
+
proof: {
|
|
82
|
+
...proofConfiguration,
|
|
83
|
+
proofValue: new TextDecoder().decode(encodeMultibase("base58btc", signature))
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
test("verifyCompoundProofDocuments() verifies the recorded maps independently", async () => {
|
|
88
|
+
for (const fixture of [map_local_create_note_default, map_local_context_conflict_default]) {
|
|
89
|
+
const input = structuredClone(fixture.documents.finalSecuredCompound);
|
|
90
|
+
const result = await verifyCompoundProofDocuments(input, limits, options);
|
|
91
|
+
assertEquals(result.status, "ok");
|
|
92
|
+
if (result.status !== "ok") continue;
|
|
93
|
+
assert(result.verified);
|
|
94
|
+
assertEquals(result.documents.map(({ path, verified }) => ({
|
|
95
|
+
path,
|
|
96
|
+
verified
|
|
97
|
+
})), [{
|
|
98
|
+
path: "/object",
|
|
99
|
+
verified: true
|
|
100
|
+
}, {
|
|
101
|
+
path: "",
|
|
102
|
+
verified: true
|
|
103
|
+
}]);
|
|
104
|
+
assertEquals(result.documents.map((document) => document.verified ? document.key.id?.href : null), [fixture.keys.inner.verificationMethod, fixture.keys.outer.verificationMethod]);
|
|
105
|
+
assertEquals(result.snapshot, input);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
test("verifyCompoundProofDocuments() does not verify an empty proof set", async () => {
|
|
109
|
+
const result = await verifyCompoundProofDocuments({
|
|
110
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
111
|
+
id: "https://example.com/notes/unsigned",
|
|
112
|
+
type: "Note"
|
|
113
|
+
}, limits, options);
|
|
114
|
+
assertEquals(result.status, "ok");
|
|
115
|
+
if (result.status !== "ok") return;
|
|
116
|
+
assertEquals(result.verified, false);
|
|
117
|
+
assertEquals(result.documents, []);
|
|
118
|
+
assertEquals(result.statistics.proofCount, 0);
|
|
119
|
+
});
|
|
120
|
+
test("verifyCompoundProofDocuments() reports tampering and proof replacement per map", async () => {
|
|
121
|
+
const tampered = structuredClone(map_local_create_note_default.documents.finalSecuredCompound);
|
|
122
|
+
asRecord(tampered.object).content = "Tampered after both proofs";
|
|
123
|
+
const tamperedResult = await verifyCompoundProofDocuments(tampered, limits, options);
|
|
124
|
+
assertEquals(tamperedResult.status, "ok");
|
|
125
|
+
if (tamperedResult.status === "ok") {
|
|
126
|
+
assertEquals(tamperedResult.verified, false);
|
|
127
|
+
assertEquals(tamperedResult.documents.map(({ path, verified }) => ({
|
|
128
|
+
path,
|
|
129
|
+
verified
|
|
130
|
+
})), [{
|
|
131
|
+
path: "/object",
|
|
132
|
+
verified: false
|
|
133
|
+
}, {
|
|
134
|
+
path: "",
|
|
135
|
+
verified: false
|
|
136
|
+
}]);
|
|
137
|
+
}
|
|
138
|
+
const replaced = structuredClone(map_local_create_note_default.documents.finalSecuredCompound);
|
|
139
|
+
asRecord(replaced.object).proof = structuredClone(map_local_create_note_default.documents.replacementInnerProof);
|
|
140
|
+
const replacedResult = await verifyCompoundProofDocuments(replaced, limits, options);
|
|
141
|
+
assertEquals(replacedResult.status, "ok");
|
|
142
|
+
if (replacedResult.status === "ok") {
|
|
143
|
+
assertEquals(replacedResult.verified, false);
|
|
144
|
+
assertEquals(replacedResult.documents.map(({ path, verified }) => ({
|
|
145
|
+
path,
|
|
146
|
+
verified
|
|
147
|
+
})), [{
|
|
148
|
+
path: "/object",
|
|
149
|
+
verified: true
|
|
150
|
+
}, {
|
|
151
|
+
path: "",
|
|
152
|
+
verified: false
|
|
153
|
+
}]);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
test("verifyCompoundProofDocuments() requires a local context on nested secured maps", async () => {
|
|
157
|
+
const input = structuredClone(map_local_create_note_default.documents.finalSecuredCompound);
|
|
158
|
+
delete asRecord(input.object)["@context"];
|
|
159
|
+
const result = await verifyCompoundProofDocuments(input, limits, options);
|
|
160
|
+
assertEquals(result.status, "ok");
|
|
161
|
+
if (result.status !== "ok") return;
|
|
162
|
+
assertEquals(result.verified, false);
|
|
163
|
+
assertEquals(result.documents[0], {
|
|
164
|
+
path: "/object",
|
|
165
|
+
id: map_local_create_note_default.documents.securedInner.id,
|
|
166
|
+
depth: 1,
|
|
167
|
+
verified: false,
|
|
168
|
+
reason: { type: "missingContext" }
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
test("verifyCompoundProofDocuments() preserves proof aliases in the JCS input", async () => {
|
|
172
|
+
const secured = await secureRawDocument({
|
|
173
|
+
"@context": [
|
|
174
|
+
"https://www.w3.org/ns/activitystreams",
|
|
175
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
176
|
+
{ integrityProof: "https://w3id.org/security#proof" }
|
|
177
|
+
],
|
|
178
|
+
id: "https://example.com/notes/alias-bound",
|
|
179
|
+
type: "Note",
|
|
180
|
+
content: "Alias-bound content",
|
|
181
|
+
integrityProof: "This value is signed data, not the direct proof."
|
|
182
|
+
}, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod);
|
|
183
|
+
const result = await verifyCompoundProofDocuments(secured, limits, options);
|
|
184
|
+
assertEquals(result.status, "ok");
|
|
185
|
+
if (result.status !== "ok") return;
|
|
186
|
+
assert(result.verified);
|
|
187
|
+
const tampered = structuredClone(secured);
|
|
188
|
+
tampered.integrityProof = "Changed after signing";
|
|
189
|
+
const tamperedResult = await verifyCompoundProofDocuments(tampered, limits, options);
|
|
190
|
+
assertEquals(tamperedResult.status, "ok");
|
|
191
|
+
if (tamperedResult.status === "ok") assertEquals(tamperedResult.verified, false);
|
|
192
|
+
});
|
|
193
|
+
test("verifyCompoundProofDocuments() preserves the received document context", async () => {
|
|
194
|
+
const proofContext = ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"];
|
|
195
|
+
const result = await verifyCompoundProofDocuments(await secureRawDocument({
|
|
196
|
+
"@context": [...proofContext, { ex: "https://example.com/ns#" }],
|
|
197
|
+
id: "https://example.com/notes/distinct-contexts",
|
|
198
|
+
type: "Note",
|
|
199
|
+
content: "The document context is part of the signed input."
|
|
200
|
+
}, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod, proofContext), limits, options);
|
|
201
|
+
assertEquals(result.status, "ok");
|
|
202
|
+
if (result.status !== "ok") return;
|
|
203
|
+
assert(result.verified);
|
|
204
|
+
});
|
|
205
|
+
test("verifyCompoundProofDocuments() authenticates only received JSON values", async () => {
|
|
206
|
+
const secured = await secureRawDocument({
|
|
207
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
208
|
+
id: "https://example.com/notes/attachment-array",
|
|
209
|
+
type: "Note",
|
|
210
|
+
attachment: ["https://example.com/images/1"]
|
|
211
|
+
}, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod);
|
|
212
|
+
const verified = await verifyCompoundProofDocuments(secured, limits, options);
|
|
213
|
+
assertEquals(verified.status, "ok");
|
|
214
|
+
if (verified.status !== "ok") return;
|
|
215
|
+
assert(verified.verified);
|
|
216
|
+
const tampered = structuredClone(secured);
|
|
217
|
+
tampered.attachment = "https://example.com/images/1";
|
|
218
|
+
const result = await verifyCompoundProofDocuments(tampered, limits, options);
|
|
219
|
+
assertEquals(result.status, "ok");
|
|
220
|
+
if (result.status === "ok") assertEquals(result.verified, false);
|
|
221
|
+
});
|
|
222
|
+
test("verifyCompoundProofDocuments() contains malformed contexts per map", async () => {
|
|
223
|
+
const valid = await secureRawDocument({
|
|
224
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
225
|
+
id: "https://example.com/notes/valid-sibling",
|
|
226
|
+
type: "Note",
|
|
227
|
+
content: "This sibling must still produce a result."
|
|
228
|
+
}, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod);
|
|
229
|
+
const malformed = structuredClone(valid);
|
|
230
|
+
malformed["@context"] = 42;
|
|
231
|
+
malformed.id = "https://example.com/notes/malformed-context";
|
|
232
|
+
malformed.proof = {
|
|
233
|
+
"@context": [],
|
|
234
|
+
"@type": ["https://w3id.org/security#DataIntegrityProof"],
|
|
235
|
+
"https://w3id.org/security#cryptosuite": [{ "@value": "eddsa-jcs-2022" }],
|
|
236
|
+
"https://w3id.org/security#verificationMethod": [{ "@id": map_local_create_note_default.keys.outer.verificationMethod }],
|
|
237
|
+
"https://w3id.org/security#proofPurpose": [{ "@id": "https://w3id.org/security#assertionMethod" }],
|
|
238
|
+
"http://purl.org/dc/terms/created": [{
|
|
239
|
+
"@value": "2023-02-24T23:36:38Z",
|
|
240
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
241
|
+
}],
|
|
242
|
+
"https://w3id.org/security#proofValue": [{ "@value": asRecord(valid.proof).proofValue }]
|
|
243
|
+
};
|
|
244
|
+
const result = await verifyCompoundProofDocuments({
|
|
245
|
+
malformed,
|
|
246
|
+
valid
|
|
247
|
+
}, limits, options);
|
|
248
|
+
assertEquals(result.status, "ok");
|
|
249
|
+
if (result.status !== "ok") return;
|
|
250
|
+
assertEquals(result.verified, false);
|
|
251
|
+
assertEquals(result.documents.map((document) => ({
|
|
252
|
+
path: document.path,
|
|
253
|
+
verified: document.verified,
|
|
254
|
+
reason: document.verified ? void 0 : document.reason.type
|
|
255
|
+
})), [{
|
|
256
|
+
path: "/malformed",
|
|
257
|
+
verified: false,
|
|
258
|
+
reason: "invalidProof"
|
|
259
|
+
}, {
|
|
260
|
+
path: "/valid",
|
|
261
|
+
verified: true,
|
|
262
|
+
reason: void 0
|
|
263
|
+
}]);
|
|
264
|
+
});
|
|
265
|
+
test("verifyCompoundProofDocuments() does not fetch proof contexts", async () => {
|
|
266
|
+
let contextLoads = 0;
|
|
267
|
+
const result = await verifyCompoundProofDocuments({
|
|
268
|
+
"@context": "https://attacker.example/context",
|
|
269
|
+
id: "https://example.com/notes/remote-context",
|
|
270
|
+
type: "Note",
|
|
271
|
+
proof: {
|
|
272
|
+
type: "DataIntegrityProof",
|
|
273
|
+
cryptosuite: "eddsa-jcs-2022",
|
|
274
|
+
verificationMethod: map_local_create_note_default.keys.outer.verificationMethod,
|
|
275
|
+
proofPurpose: "assertionMethod",
|
|
276
|
+
created: "2023-02-24T23:36:38Z",
|
|
277
|
+
proofValue: map_local_create_note_default.proofValues.outer
|
|
278
|
+
}
|
|
279
|
+
}, limits, {
|
|
280
|
+
...options,
|
|
281
|
+
contextLoader() {
|
|
282
|
+
contextLoads++;
|
|
283
|
+
throw new TypeError("unexpected context fetch");
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
assertEquals(result.status, "ok");
|
|
287
|
+
if (result.status === "ok") assertEquals(result.verified, false);
|
|
288
|
+
assertEquals(contextLoads, 0);
|
|
289
|
+
});
|
|
290
|
+
test("verifyCompoundPortableObjectProofs() applies policy to every portable map", async () => {
|
|
291
|
+
let contextLoads = 0;
|
|
292
|
+
const result = await verifyCompoundPortableObjectProofs(map_local_create_note_default.documents.finalSecuredCompound, limits, {
|
|
293
|
+
...options,
|
|
294
|
+
contextLoader() {
|
|
295
|
+
contextLoads++;
|
|
296
|
+
throw new TypeError("unexpected context fetch");
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
assertEquals(result.status, "ok");
|
|
300
|
+
if (result.status !== "ok") return;
|
|
301
|
+
assert(result.verified);
|
|
302
|
+
assertEquals(contextLoads, 0);
|
|
303
|
+
assertEquals(result.portableObjects.map((document) => ({
|
|
304
|
+
path: document.path,
|
|
305
|
+
verified: document.verified,
|
|
306
|
+
keys: document.verified ? document.keys.map((key) => key.id?.href) : []
|
|
307
|
+
})), [{
|
|
308
|
+
path: "/object",
|
|
309
|
+
verified: true,
|
|
310
|
+
keys: [map_local_create_note_default.keys.inner.verificationMethod]
|
|
311
|
+
}, {
|
|
312
|
+
path: "",
|
|
313
|
+
verified: true,
|
|
314
|
+
keys: [map_local_create_note_default.keys.outer.verificationMethod]
|
|
315
|
+
}]);
|
|
316
|
+
});
|
|
317
|
+
test("verifyCompoundPortableObjectProofs() reports unsigned portable maps", async () => {
|
|
318
|
+
const context = ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"];
|
|
319
|
+
const result = await verifyCompoundPortableObjectProofs({
|
|
320
|
+
"@context": context,
|
|
321
|
+
id: map_local_create_note_default.documents.outerUnsecuredDocument.id,
|
|
322
|
+
type: "Create",
|
|
323
|
+
actor: map_local_create_note_default.documents.outerUnsecuredDocument.actor,
|
|
324
|
+
object: {
|
|
325
|
+
"@context": structuredClone(context),
|
|
326
|
+
id: map_local_create_note_default.documents.innerUnsecuredDocument.id,
|
|
327
|
+
type: "Note",
|
|
328
|
+
attributedTo: map_local_create_note_default.documents.innerUnsecuredDocument.attributedTo,
|
|
329
|
+
content: "Unsigned portable child"
|
|
330
|
+
}
|
|
331
|
+
}, limits, options);
|
|
332
|
+
assertEquals(result.status, "ok");
|
|
333
|
+
if (result.status !== "ok") return;
|
|
334
|
+
assertEquals(result.verified, false);
|
|
335
|
+
assertEquals(result.proofs, []);
|
|
336
|
+
assertEquals(result.portableObjects.map((document) => ({
|
|
337
|
+
path: document.path,
|
|
338
|
+
verified: document.verified,
|
|
339
|
+
reason: document.verified ? void 0 : document.reason.type
|
|
340
|
+
})), [{
|
|
341
|
+
path: "/object",
|
|
342
|
+
verified: false,
|
|
343
|
+
reason: "missingProof"
|
|
344
|
+
}, {
|
|
345
|
+
path: "",
|
|
346
|
+
verified: false,
|
|
347
|
+
reason: "missingProof"
|
|
348
|
+
}]);
|
|
349
|
+
});
|
|
350
|
+
test("verifyCompoundPortableObjectProofs() reports policy before crypto failure", async () => {
|
|
351
|
+
const mismatched = structuredClone(map_local_create_note_default.documents.finalSecuredCompound);
|
|
352
|
+
asRecord(mismatched.object).id = map_local_create_note_default.documents.outerUnsecuredDocument.id;
|
|
353
|
+
const result = await verifyCompoundPortableObjectProofs(mismatched, limits, options);
|
|
354
|
+
assertEquals(result.status, "ok");
|
|
355
|
+
if (result.status !== "ok") return;
|
|
356
|
+
assertEquals(result.verified, false);
|
|
357
|
+
const inner = result.portableObjects.find(({ path }) => path === "/object");
|
|
358
|
+
assert(inner != null && !inner.verified);
|
|
359
|
+
assertEquals(inner.reason.type, "verificationMethodMismatch");
|
|
360
|
+
});
|
|
361
|
+
test("verifyCompoundPortableObjectProofs() finds unsigned portable maps in arrays", async () => {
|
|
362
|
+
const result = await verifyCompoundPortableObjectProofs({
|
|
363
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
364
|
+
id: "https://social.example/activities/1",
|
|
365
|
+
type: "Create",
|
|
366
|
+
attachment: [{
|
|
367
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
368
|
+
id: map_local_create_note_default.documents.innerUnsecuredDocument.id,
|
|
369
|
+
type: "Note",
|
|
370
|
+
content: "Unsigned portable attachment"
|
|
371
|
+
}]
|
|
372
|
+
}, limits, options);
|
|
373
|
+
assertEquals(result.status, "ok");
|
|
374
|
+
if (result.status !== "ok") return;
|
|
375
|
+
assertEquals(result.verified, false);
|
|
376
|
+
assertEquals(result.portableObjects.map((document) => ({
|
|
377
|
+
path: document.path,
|
|
378
|
+
reason: document.verified ? void 0 : document.reason.type
|
|
379
|
+
})), [{
|
|
380
|
+
path: "/attachment/0",
|
|
381
|
+
reason: "missingProof"
|
|
382
|
+
}]);
|
|
383
|
+
});
|
|
384
|
+
test("verifyCompoundPortableObjectProofs() ignores context definitions", async () => {
|
|
385
|
+
const result = await verifyCompoundPortableObjectProofs({
|
|
386
|
+
"@context": { portable: {
|
|
387
|
+
"@id": map_local_create_note_default.documents.innerUnsecuredDocument.id,
|
|
388
|
+
"@context": { proof: "https://w3id.org/security#proof" }
|
|
389
|
+
} },
|
|
390
|
+
id: "https://social.example/objects/1",
|
|
391
|
+
type: "Note"
|
|
392
|
+
}, limits, options);
|
|
393
|
+
assertEquals(result.status, "ok");
|
|
394
|
+
if (result.status !== "ok") return;
|
|
395
|
+
assertEquals(result.verified, false);
|
|
396
|
+
assertEquals(result.proofs, []);
|
|
397
|
+
assertEquals(result.portableObjects, []);
|
|
398
|
+
});
|
|
399
|
+
test("verifyCompoundPortableObjectProofs() contains malformed portable IDs", async () => {
|
|
400
|
+
const result = await verifyCompoundPortableObjectProofs({
|
|
401
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
402
|
+
id: "ap://did%ZZkey/objects/1",
|
|
403
|
+
type: "Note"
|
|
404
|
+
}, limits, options);
|
|
405
|
+
assertEquals(result.status, "ok");
|
|
406
|
+
if (result.status !== "ok") return;
|
|
407
|
+
assertEquals(result.verified, false);
|
|
408
|
+
assertEquals(result.portableObjects.length, 1);
|
|
409
|
+
const [object] = result.portableObjects;
|
|
410
|
+
assert(!object.verified);
|
|
411
|
+
assertEquals(object.reason, { type: "invalidPortableObject" });
|
|
412
|
+
});
|
|
413
|
+
test("verifyPortableObjectProofPolicy() binds policy to the verified key", async () => {
|
|
414
|
+
const outerKey = await verifyMapLocalProof(map_local_create_note_default.documents.finalSecuredCompound, options);
|
|
415
|
+
assert(outerKey != null);
|
|
416
|
+
assertEquals(await verifyPortableObjectProofPolicy(map_local_create_note_default.documents.securedInner, outerKey, options), {
|
|
417
|
+
verified: false,
|
|
418
|
+
reason: {
|
|
419
|
+
type: "invalidProof",
|
|
420
|
+
proofIndex: 0
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
test("verifyPortableObjectProofPolicy() binds policy to the verified proof", async () => {
|
|
425
|
+
const context = createInlineProofContext();
|
|
426
|
+
context.proof = {
|
|
427
|
+
"@id": "https://w3id.org/security#proof",
|
|
428
|
+
"@context": { assertionMethod: "https://w3id.org/security#authentication" }
|
|
429
|
+
};
|
|
430
|
+
const secured = await secureRawDocument({
|
|
431
|
+
"@context": context,
|
|
432
|
+
id: map_local_create_note_default.documents.innerUnsecuredDocument.id,
|
|
433
|
+
type: "Note",
|
|
434
|
+
attributedTo: map_local_create_note_default.documents.innerUnsecuredDocument.attributedTo,
|
|
435
|
+
content: "Property-scoped proof purpose"
|
|
436
|
+
}, map_local_create_note_default.keys.inner.testPrivateKeyJwk, map_local_create_note_default.keys.inner.verificationMethod);
|
|
437
|
+
delete asRecord(secured.proof)["@context"];
|
|
438
|
+
const key = await verifyMapLocalProof(secured, options);
|
|
439
|
+
assert(key != null);
|
|
440
|
+
assertEquals(await verifyPortableObjectProofPolicy(secured, key, options), {
|
|
441
|
+
verified: false,
|
|
442
|
+
reason: {
|
|
443
|
+
type: "invalidProof",
|
|
444
|
+
proofIndex: 0
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
test("verifyCompoundPortableObjectProofs() reports the policy-validated ID", async () => {
|
|
449
|
+
const context = createInlineProofContext();
|
|
450
|
+
context.id = "https://example.com/id";
|
|
451
|
+
const result = await verifyCompoundPortableObjectProofs(await secureRawDocument({
|
|
452
|
+
"@context": context,
|
|
453
|
+
id: map_local_create_note_default.documents.outerUnsecuredDocument.id,
|
|
454
|
+
"@id": map_local_create_note_default.documents.innerUnsecuredDocument.id,
|
|
455
|
+
type: "Note",
|
|
456
|
+
content: "Conflicting raw ID"
|
|
457
|
+
}, map_local_create_note_default.keys.inner.testPrivateKeyJwk, map_local_create_note_default.keys.inner.verificationMethod), limits, options);
|
|
458
|
+
assertEquals(result.status, "ok");
|
|
459
|
+
if (result.status !== "ok") return;
|
|
460
|
+
assert(result.verified);
|
|
461
|
+
assertEquals(result.portableObjects.length, 1);
|
|
462
|
+
assertEquals(result.portableObjects[0].id, map_local_create_note_default.documents.innerUnsecuredDocument.id);
|
|
463
|
+
});
|
|
464
|
+
//#endregion
|
|
465
|
+
export {};
|