@fedify/fedify 2.4.0-dev.1727 → 2.4.0-dev.1740

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.
Files changed (57) hide show
  1. package/dist/{builder-f5-YwHfj.mjs → builder-Bw6LVKGw.mjs} +2 -2
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{deno-Deyoxesx.mjs → deno-tRdp8MCl.mjs} +1 -1
  4. package/dist/{docloader-B3kuDBg9.mjs → docloader-Dg_7gMC7.mjs} +2 -2
  5. package/dist/federation/builder.test.mjs +1 -1
  6. package/dist/federation/handler.test.mjs +3 -3
  7. package/dist/federation/idempotency.test.mjs +2 -2
  8. package/dist/federation/metrics.test.mjs +1 -1
  9. package/dist/federation/middleware.test.mjs +7 -7
  10. package/dist/federation/mod.cjs +1 -1
  11. package/dist/federation/mod.js +1 -1
  12. package/dist/federation/send.test.mjs +3 -3
  13. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  14. package/dist/federation/tasks/tasks.test.mjs +2 -2
  15. package/dist/federation/temporal.test.mjs +1 -1
  16. package/dist/federation/webfinger.test.mjs +1 -1
  17. package/dist/{federation-BzRjeOky.cjs → federation-BJBpt_Am.cjs} +3 -3
  18. package/dist/{http-Bw6s_dOB.js → http-BIxGa6AG.js} +1 -1
  19. package/dist/{http-TjhZfiSN.mjs → http-Day1kcpT.mjs} +3 -3
  20. package/dist/{http-AyKngJ_h.cjs → http-DggG1scN.cjs} +1 -1
  21. package/dist/{key-b4kf4yZT.mjs → key-igh0AMsL.mjs} +2 -2
  22. package/dist/{kv-cache-CQsh-KUw.js → kv-cache-BIAB0HKx.js} +1 -1
  23. package/dist/{kv-cache-CpILvhUV.mjs → kv-cache-Bz6aQOOX.mjs} +1 -1
  24. package/dist/{kv-cache-BTVaZrf-.cjs → kv-cache-C4U_uBqD.cjs} +1 -1
  25. package/dist/{ld-CKujkAfG.mjs → ld-DnT9sCWa.mjs} +3 -3
  26. package/dist/{metrics-AlCGTDrr.mjs → metrics-DbdNnWIt.mjs} +1 -1
  27. package/dist/{middleware-LPbZz6-1.mjs → middleware-B6GweVcg.mjs} +1 -1
  28. package/dist/{middleware-CZcuedl-.js → middleware-CnsPXG0O.js} +3 -3
  29. package/dist/{middleware-C9Y5lLZN.mjs → middleware-DPIS4fsB.mjs} +12 -12
  30. package/dist/{mod-CuDDaIIt.d.cts → mod-CnGLNoVw.d.cts} +85 -3
  31. package/dist/{mod-D-IhOEOP.d.ts → mod-DzRXACM5.d.ts} +85 -3
  32. package/dist/mod.cjs +5 -4
  33. package/dist/mod.d.cts +3 -3
  34. package/dist/mod.d.ts +3 -3
  35. package/dist/mod.js +5 -5
  36. package/dist/nodeinfo/handler.test.mjs +1 -1
  37. package/dist/{owner-D1O8v8FB.mjs → owner-B3ef8GfJ.mjs} +2 -2
  38. package/dist/{proof-BTBolSnd.js → proof-BBLQXRWw.js} +603 -31
  39. package/dist/{proof-DcSm-2eG.cjs → proof-Cqk2TcLX.cjs} +607 -29
  40. package/dist/proof-DVoDZ8d7.mjs +890 -0
  41. package/dist/{send-DG7JG280.mjs → send-PbBSZ7Qk.mjs} +3 -3
  42. package/dist/sig/http.test.mjs +2 -2
  43. package/dist/sig/key.test.mjs +1 -1
  44. package/dist/sig/ld.test.mjs +2 -2
  45. package/dist/sig/mod.cjs +3 -2
  46. package/dist/sig/mod.d.cts +2 -2
  47. package/dist/sig/mod.d.ts +2 -2
  48. package/dist/sig/mod.js +3 -3
  49. package/dist/sig/owner.test.mjs +1 -1
  50. package/dist/sig/proof.test.mjs +956 -8
  51. package/dist/{temporal-DA3jx8x1.mjs → temporal-DLQIjuTn.mjs} +1 -1
  52. package/dist/utils/docloader.test.mjs +2 -2
  53. package/dist/utils/kv-cache.test.mjs +1 -1
  54. package/dist/utils/mod.cjs +1 -1
  55. package/dist/utils/mod.js +1 -1
  56. package/package.json +6 -6
  57. package/dist/proof-DeMR7Akd.mjs +0 -316
@@ -1,6 +1,6 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
- const require_http = require("./http-AyKngJ_h.cjs");
3
+ const require_http = require("./http-DggG1scN.cjs");
4
4
  let _logtape_logtape = require("@logtape/logtape");
5
5
  let _fedify_vocab = require("@fedify/vocab");
6
6
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -1129,6 +1129,16 @@ const logger = (0, _logtape_logtape.getLogger)([
1129
1129
  "sig",
1130
1130
  "proof"
1131
1131
  ]);
1132
+ const SECURITY_NAMESPACE = "https://w3id.org/security#";
1133
+ const SECURITY_PROOF = `${SECURITY_NAMESPACE}proof`;
1134
+ const SECURITY_PROOF_VALUE = `${SECURITY_NAMESPACE}proofValue`;
1135
+ const DATA_INTEGRITY_PROOF = `${SECURITY_NAMESPACE}DataIntegrityProof`;
1136
+ const SECURITY_VERIFICATION_METHOD = `${SECURITY_NAMESPACE}verificationMethod`;
1137
+ const SECURITY_EXPIRATION = `${SECURITY_NAMESPACE}expiration`;
1138
+ const SECURITY_DOMAIN = `${SECURITY_NAMESPACE}domain`;
1139
+ const SECURITY_CHALLENGE = `${SECURITY_NAMESPACE}challenge`;
1140
+ const SECURITY_NONCE = `${SECURITY_NAMESPACE}nonce`;
1141
+ const SECURITY_PREVIOUS_PROOF = `${SECURITY_NAMESPACE}previousProof`;
1132
1142
  /**
1133
1143
  * Checks if the given JSON-LD document has a DataIntegrityProof-like object,
1134
1144
  * without fully deserializing it into vocabulary classes.
@@ -1240,15 +1250,21 @@ async function signObject(object, privateKey, keyId, options = {}) {
1240
1250
  }
1241
1251
  /**
1242
1252
  * Verifies the given proof for the object.
1243
- * @param jsonLd The JSON-LD object to verify the proof for. If it contains
1244
- * any proofs, they will be ignored.
1253
+ * @param jsonLd The JSON-LD object to verify the proof for. Its proof
1254
+ * properties are excluded from the message, but matching proof
1255
+ * occurrences are used to authenticate the received proof
1256
+ * configuration.
1245
1257
  * @param proof The proof to verify.
1246
1258
  * @param options Additional options. See also {@link VerifyProofOptions}.
1247
1259
  * @returns The public key that was used to sign the proof, or `null` if the
1248
1260
  * proof is invalid.
1249
1261
  * @since 0.10.0
1262
+ * @since 2.4.0 Matching proof configurations are authenticated.
1250
1263
  */
1251
1264
  async function verifyProof(jsonLd, proof, options = {}) {
1265
+ return await verifyProofWithMessageDigestCache(jsonLd, proof, options);
1266
+ }
1267
+ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, messageDigestCache = {}, rawProofCandidate) {
1252
1268
  return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(require_http.name, require_http.version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
1253
1269
  const start = performance.now();
1254
1270
  let verified = false;
@@ -1260,7 +1276,7 @@ async function verifyProof(jsonLd, proof, options = {}) {
1260
1276
  if (proof.proofValue != null) span.setAttribute("object_integrity_proofs.signature", (0, byte_encodings_hex.encodeHex)(proof.proofValue));
1261
1277
  }
1262
1278
  try {
1263
- const key = await verifyProofInternal(jsonLd, proof, options);
1279
+ const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate);
1264
1280
  if (key == null) span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
1265
1281
  else verified = true;
1266
1282
  return key;
@@ -1278,23 +1294,313 @@ async function verifyProof(jsonLd, proof, options = {}) {
1278
1294
  }
1279
1295
  });
1280
1296
  }
1281
- async function verifyProofInternal(jsonLd, proof, options) {
1282
- if (typeof jsonLd !== "object" || jsonLd == null || Array.isArray(jsonLd) || proof.cryptosuite !== "eddsa-jcs-2022" || proof.verificationMethodId == null || proof.proofPurpose !== "assertionMethod" || proof.proofValue == null || proof.created == null) return null;
1283
- const publicKeyPromise = require_http.measureSignatureKeyFetch(options.meterProvider, "object_integrity", () => require_http.fetchKey(proof.verificationMethodId, _fedify_vocab.Multikey, options));
1284
- const proofConfig = {
1285
- "@context": jsonLd["@context"],
1286
- type: "DataIntegrityProof",
1287
- cryptosuite: proof.cryptosuite,
1288
- verificationMethod: proof.verificationMethodId.href,
1289
- proofPurpose: proof.proofPurpose,
1290
- created: proof.created.toString()
1291
- };
1297
+ function expandContextPropertyIri(activeContext, key) {
1298
+ const termId = _fedify_vocab_runtime_jsonld.default.getContextValue(activeContext, key, "@id");
1299
+ if (termId != null) return termId;
1300
+ const colon = key.indexOf(":");
1301
+ if (colon < 1) return key;
1302
+ const prefix = key.substring(0, colon);
1303
+ const suffix = key.substring(colon + 1);
1304
+ if (prefix === "_" || suffix.startsWith("//")) return key;
1305
+ const mapping = _fedify_vocab_runtime_jsonld.default.getContextValue(activeContext, prefix);
1306
+ if (typeof mapping === "object" && mapping != null && mapping._prefix === true && typeof mapping["@id"] === "string") return mapping["@id"] + suffix;
1307
+ return key;
1308
+ }
1309
+ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false) {
1310
+ const names = new Set(defaults);
1311
+ const context = jsonLd["@context"] ?? inheritedContext;
1312
+ if (context == null) return names;
1313
+ try {
1314
+ const options = { documentLoader };
1315
+ let activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(null, null, options);
1316
+ activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(activeContext, context, options);
1317
+ const typeScopedContext = activeContext;
1318
+ for (const key of globalThis.Object.keys(jsonLd).sort()) {
1319
+ if (key !== "@type" && expandContextPropertyIri(activeContext, key) !== "@type") continue;
1320
+ const value = jsonLd[key];
1321
+ const types = Array.isArray(value) ? value.slice().sort() : [value];
1322
+ for (const type of types) {
1323
+ if (typeof type !== "string") continue;
1324
+ const scopedContext = _fedify_vocab_runtime_jsonld.default.getContextValue(typeScopedContext, type, "@context");
1325
+ if (scopedContext != null) activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(activeContext, scopedContext, options);
1326
+ }
1327
+ }
1328
+ for (const key of globalThis.Object.keys(jsonLd)) if (expandContextPropertyIri(activeContext, key) === propertyIri) names.add(key);
1329
+ } catch {
1330
+ if (rejectOnContextError) return null;
1331
+ }
1332
+ return names;
1333
+ }
1334
+ async function getProofPropertyNames(jsonLd, documentLoader = preloadedOnlyDocumentLoader) {
1335
+ return await getJsonLdPropertyNames(jsonLd, SECURITY_PROOF, ["proof", SECURITY_PROOF], documentLoader) ?? /* @__PURE__ */ new Set(["proof", SECURITY_PROOF]);
1336
+ }
1337
+ async function createProofMessageDigests(jsonLd, proofContextLoader, context) {
1338
+ const msg = { ...jsonLd };
1339
+ for (const property of await getProofPropertyNames(msg, proofContextLoader)) delete msg[property];
1340
+ if (context != null) msg["@context"] = structuredClone(context);
1292
1341
  const encoder = new TextEncoder();
1293
- const proofBytes = encoder.encode((0, json_canon.default)(proofConfig));
1342
+ const digest = async (value) => {
1343
+ const bytes = encoder.encode((0, json_canon.default)(value));
1344
+ return await crypto.subtle.digest("SHA-256", bytes);
1345
+ };
1346
+ const onWire = await digest(msg);
1347
+ let normalizedPromise;
1348
+ return {
1349
+ onWire,
1350
+ normalized() {
1351
+ normalizedPromise ??= (async () => {
1352
+ const normalized = await normalizeOutgoingActivityJsonLd(msg, preloadedOnlyDocumentLoader);
1353
+ return normalized === msg ? null : await digest(normalized);
1354
+ })();
1355
+ return normalizedPromise;
1356
+ }
1357
+ };
1358
+ }
1359
+ function contextValues(context) {
1360
+ return Array.isArray(context) ? context : [context];
1361
+ }
1362
+ function equalJsonValues(left, right) {
1363
+ try {
1364
+ return (0, json_canon.default)(left) === (0, json_canon.default)(right);
1365
+ } catch {
1366
+ return false;
1367
+ }
1368
+ }
1369
+ function contextStartsWith(documentContext, proofContext) {
1370
+ if (documentContext == null) return false;
1371
+ const documentValues = contextValues(documentContext);
1372
+ const proofValues = contextValues(proofContext);
1373
+ return proofValues.length <= documentValues.length && proofValues.every((value, index) => equalJsonValues(value, documentValues[index]));
1374
+ }
1375
+ function appendProofValues(values, value) {
1376
+ if (Array.isArray(value)) for (const item of value) appendProofValues(values, item);
1377
+ else if (isJsonLdNode(value) && Array.isArray(value["@graph"])) for (const item of value["@graph"]) appendProofValues(values, item);
1378
+ else values.push(value);
1379
+ }
1380
+ async function getRawProofValues(jsonLd, documentLoader) {
1381
+ const propertyNames = await getProofPropertyNames(jsonLd, documentLoader);
1382
+ const values = [];
1383
+ for (const [property, value] of globalThis.Object.entries(jsonLd)) if (propertyNames.has(property)) appendProofValues(values, value);
1384
+ return values;
1385
+ }
1386
+ function sameBytes(left, right) {
1387
+ if (left == null || right == null) return left === right;
1388
+ return left.length === right.length && left.every((value, index) => value === right[index]);
1389
+ }
1390
+ function sameProof(left, right) {
1391
+ 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();
1392
+ }
1393
+ function normalizeDocumentUrl(url) {
1394
+ try {
1395
+ return (0, _fedify_vocab_runtime.formatIri)((0, _fedify_vocab_runtime.parseIri)(url));
1396
+ } catch {
1397
+ return URL.canParse(url) ? new URL(url).href : url;
1398
+ }
1399
+ }
1400
+ function getRawProofReference(value) {
1401
+ const node = Array.isArray(value) && value.length === 1 ? value[0] : value;
1402
+ if (typeof node === "string") return normalizeDocumentUrl(node);
1403
+ if (!isJsonLdNode(node)) return void 0;
1404
+ const id = node["@id"] ?? node.id;
1405
+ return typeof id === "string" ? normalizeDocumentUrl(id) : void 0;
1406
+ }
1407
+ async function parseRawProofCandidates(jsonLd, values, options, documentLoader) {
1408
+ const candidates = [];
1409
+ for (const value of values) {
1410
+ let parsed = null;
1411
+ if (isJsonLdNode(value)) {
1412
+ const proofJsonLd = value["@context"] == null && jsonLd["@context"] != null ? {
1413
+ "@context": jsonLd["@context"],
1414
+ ...value
1415
+ } : value;
1416
+ try {
1417
+ parsed = await _fedify_vocab.DataIntegrityProof.fromJsonLd(proofJsonLd, {
1418
+ ...options,
1419
+ contextLoader: documentLoader
1420
+ });
1421
+ } catch {}
1422
+ }
1423
+ candidates.push({
1424
+ value,
1425
+ proof: parsed,
1426
+ reference: getRawProofReference(value) ?? parsed?.id?.href
1427
+ });
1428
+ }
1429
+ return candidates;
1430
+ }
1431
+ async function findRawProofCandidate(jsonLd, proof, options, documentLoader) {
1432
+ const candidates = await parseRawProofCandidates(jsonLd, await getRawProofValues(jsonLd, documentLoader), options, documentLoader);
1433
+ const matches = [];
1434
+ for (const candidate of candidates) if (candidate.proof != null && sameProof(candidate.proof, proof)) matches.push(candidate);
1435
+ if (matches.length < 1) return void 0;
1436
+ const first = matches[0];
1437
+ const configurations = await Promise.all(matches.map((candidate) => normalizeProofConfiguration(candidate.value, jsonLd["@context"], documentLoader)));
1438
+ const firstConfiguration = configurations[0];
1439
+ return firstConfiguration != null && configurations.every((configuration) => configuration != null && equalJsonValues(configuration.value, firstConfiguration.value)) ? first : null;
1440
+ }
1441
+ function takeRawProofCandidate(pool, proof) {
1442
+ for (let index = 0; index < pool.candidates.length; index++) {
1443
+ if (pool.used.has(index)) continue;
1444
+ const candidate = pool.candidates[index];
1445
+ if (candidate.proof != null && sameProof(candidate.proof, proof)) {
1446
+ pool.used.add(index);
1447
+ return candidate;
1448
+ }
1449
+ }
1450
+ }
1451
+ const STANDARD_COMPACT_PROOF_PROPERTIES = /* @__PURE__ */ new Set([
1452
+ "@context",
1453
+ "type",
1454
+ "cryptosuite",
1455
+ "verificationMethod",
1456
+ "proofPurpose",
1457
+ "proofValue",
1458
+ "created"
1459
+ ]);
1460
+ const STANDARD_EXPANDED_PROOF_PROPERTIES = /* @__PURE__ */ new Set([
1461
+ "@context",
1462
+ "@type",
1463
+ `${SECURITY_NAMESPACE}cryptosuite`,
1464
+ SECURITY_VERIFICATION_METHOD,
1465
+ `${SECURITY_NAMESPACE}proofPurpose`,
1466
+ `${SECURITY_NAMESPACE}proofValue`,
1467
+ "http://purl.org/dc/terms/created"
1468
+ ]);
1469
+ function hasAdditionalProofOptions(value) {
1470
+ const node = Array.isArray(value) && value.length === 1 ? value[0] : value;
1471
+ return isJsonLdNode(node) && globalThis.Object.keys(node).some((property) => !STANDARD_COMPACT_PROOF_PROPERTIES.has(property) && !STANDARD_EXPANDED_PROOF_PROPERTIES.has(property));
1472
+ }
1473
+ function isExpandedJsonLdNode(value) {
1474
+ const properties = globalThis.Object.keys(value).filter((key) => !key.startsWith("@"));
1475
+ return properties.length > 0 && properties.every((property) => URL.canParse(property));
1476
+ }
1477
+ async function normalizeProofConfiguration(rawProof, documentContext, documentLoader) {
1478
+ let node = Array.isArray(rawProof) && rawProof.length === 1 ? rawProof[0] : rawProof;
1479
+ if (!isJsonLdNode(node)) return null;
1480
+ if (isExpandedJsonLdNode(node)) {
1481
+ if (documentContext == null) return null;
1482
+ node = await _fedify_vocab_runtime_jsonld.default.compact(node, documentContext, { documentLoader });
1483
+ if (!isJsonLdNode(node)) return null;
1484
+ } else node = structuredClone(node);
1485
+ const receivedContext = node["@context"];
1486
+ if (receivedContext != null && !contextStartsWith(documentContext, receivedContext)) return null;
1487
+ const context = receivedContext ?? documentContext;
1488
+ if (context != null) node["@context"] = structuredClone(context);
1489
+ const proofValueProperties = await getJsonLdPropertyNames(node, SECURITY_PROOF_VALUE, ["proofValue", SECURITY_PROOF_VALUE], documentLoader, context) ?? /* @__PURE__ */ new Set(["proofValue", SECURITY_PROOF_VALUE]);
1490
+ for (const property of proofValueProperties) delete node[property];
1491
+ return {
1492
+ value: node,
1493
+ context
1494
+ };
1495
+ }
1496
+ async function createProofConfiguration(jsonLd, proof, options, documentLoader, rawProofCandidate) {
1497
+ let rawProof;
1498
+ if (rawProofCandidate == null) {
1499
+ const match = await findRawProofCandidate(jsonLd, proof, options, documentLoader);
1500
+ if (match === null) return null;
1501
+ rawProof = match?.value;
1502
+ } else rawProof = rawProofCandidate.value;
1503
+ if (rawProof == null) {
1504
+ const serializedProof = await proof.toJsonLd();
1505
+ if (hasAdditionalProofOptions(serializedProof)) rawProof = serializedProof;
1506
+ }
1507
+ if (rawProof != null) return await normalizeProofConfiguration(rawProof, jsonLd["@context"], documentLoader);
1508
+ const context = jsonLd["@context"];
1509
+ return {
1510
+ value: {
1511
+ ...context == null ? {} : { "@context": context },
1512
+ type: "DataIntegrityProof",
1513
+ cryptosuite: proof.cryptosuite,
1514
+ verificationMethod: proof.verificationMethodId.href,
1515
+ proofPurpose: proof.proofPurpose,
1516
+ created: proof.created.toString()
1517
+ },
1518
+ context
1519
+ };
1520
+ }
1521
+ const KNOWN_PROOF_CONFIGURATION_PROPERTIES = /* @__PURE__ */ new Set([
1522
+ "@context",
1523
+ "@id",
1524
+ "@type",
1525
+ "id",
1526
+ "type",
1527
+ "cryptosuite",
1528
+ `${SECURITY_NAMESPACE}cryptosuite`,
1529
+ "verificationMethod",
1530
+ SECURITY_VERIFICATION_METHOD,
1531
+ "proofPurpose",
1532
+ `${SECURITY_NAMESPACE}proofPurpose`,
1533
+ "created",
1534
+ "http://purl.org/dc/terms/created",
1535
+ "expires",
1536
+ SECURITY_EXPIRATION,
1537
+ "domain",
1538
+ SECURITY_DOMAIN,
1539
+ "challenge",
1540
+ SECURITY_CHALLENGE,
1541
+ "nonce",
1542
+ SECURITY_NONCE,
1543
+ "previousProof",
1544
+ SECURITY_PREVIOUS_PROOF
1545
+ ]);
1546
+ async function getProofOption(proofConfig, propertyIri, defaults, documentLoader) {
1547
+ let names = await getJsonLdPropertyNames(proofConfig, propertyIri, defaults, documentLoader, proofConfig["@context"], true);
1548
+ if (names == null) {
1549
+ if (globalThis.Object.keys(proofConfig).some((property) => !KNOWN_PROOF_CONFIGURATION_PROPERTIES.has(property))) return null;
1550
+ names = new Set(defaults);
1551
+ }
1552
+ const present = globalThis.Object.keys(proofConfig).filter((property) => names.has(property));
1553
+ if (present.length > 1) return null;
1554
+ return present.length < 1 ? { present: false } : {
1555
+ present: true,
1556
+ value: proofConfig[present[0]]
1557
+ };
1558
+ }
1559
+ function parseStringSet(value) {
1560
+ if (typeof value === "string") return /* @__PURE__ */ new Set([value]);
1561
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return null;
1562
+ return new Set(value);
1563
+ }
1564
+ function equalStringSets(left, right) {
1565
+ return left.size === right.size && [...left].every((value) => right.has(value));
1566
+ }
1567
+ async function hasValidProofOptions(proofConfig, options, documentLoader) {
1568
+ const expires = await getProofOption(proofConfig, SECURITY_EXPIRATION, ["expires", SECURITY_EXPIRATION], documentLoader);
1569
+ if (expires == null) return false;
1570
+ if (expires.present) {
1571
+ if (typeof expires.value !== "string") return false;
1572
+ let expiration;
1573
+ try {
1574
+ expiration = Temporal.Instant.from(expires.value);
1575
+ } catch {
1576
+ return false;
1577
+ }
1578
+ if (Temporal.Instant.compare(Temporal.Now.instant(), expiration) >= 0) return false;
1579
+ }
1580
+ const domain = await getProofOption(proofConfig, SECURITY_DOMAIN, ["domain", SECURITY_DOMAIN], documentLoader);
1581
+ if (domain == null) return false;
1582
+ const proofDomains = domain.present ? parseStringSet(domain.value) : null;
1583
+ if (domain.present && proofDomains == null) return false;
1584
+ if (options.domain != null) {
1585
+ const expectedDomains = parseStringSet(options.domain);
1586
+ if (expectedDomains == null || proofDomains == null || !equalStringSets(proofDomains, expectedDomains)) return false;
1587
+ }
1588
+ const challenge = await getProofOption(proofConfig, SECURITY_CHALLENGE, ["challenge", SECURITY_CHALLENGE], documentLoader);
1589
+ if (challenge == null || challenge.present && typeof challenge.value !== "string" || options.challenge != null && (!challenge.present || challenge.value !== options.challenge)) return false;
1590
+ const nonce = await getProofOption(proofConfig, SECURITY_NONCE, ["nonce", SECURITY_NONCE], documentLoader);
1591
+ if (nonce == null || nonce.present && typeof nonce.value !== "string") return false;
1592
+ const previousProof = await getProofOption(proofConfig, SECURITY_PREVIOUS_PROOF, ["previousProof", SECURITY_PREVIOUS_PROOF], documentLoader);
1593
+ if (previousProof == null || previousProof.present && typeof previousProof.value !== "string" && (!Array.isArray(previousProof.value) || previousProof.value.some((item) => typeof item !== "string"))) return false;
1594
+ return true;
1595
+ }
1596
+ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate) {
1597
+ if (!isJsonLdNode(jsonLd) || proof.cryptosuite !== "eddsa-jcs-2022" || proof.verificationMethodId == null || proof.proofPurpose !== "assertionMethod" || proof.proofValue == null || proof.created == null) return null;
1598
+ const proofContextLoader = messageDigestCache.proofContextLoader ?? preloadedOnlyDocumentLoader;
1599
+ const proofConfiguration = await createProofConfiguration(jsonLd, proof, options, proofContextLoader, rawProofCandidate);
1600
+ if (proofConfiguration == null || !await hasValidProofOptions(proofConfiguration.value, options, proofContextLoader)) return null;
1601
+ const publicKeyPromise = require_http.measureSignatureKeyFetch(options.meterProvider, "object_integrity", () => require_http.fetchKey(proof.verificationMethodId, _fedify_vocab.Multikey, options));
1602
+ const proofBytes = new TextEncoder().encode((0, json_canon.default)(proofConfiguration.value));
1294
1603
  const proofDigest = await crypto.subtle.digest("SHA-256", proofBytes);
1295
- const msg = { ...jsonLd };
1296
- if ("proof" in msg) delete msg.proof;
1297
- if ("https://w3id.org/security#proof" in msg) delete msg["https://w3id.org/security#proof"];
1298
1604
  let fetchedKey;
1299
1605
  try {
1300
1606
  fetchedKey = await publicKeyPromise;
@@ -1326,7 +1632,7 @@ async function verifyProofInternal(jsonLd, proof, options) {
1326
1632
  get: () => Promise.resolve(void 0),
1327
1633
  set: async (keyId, key) => await options.keyCache?.set(keyId, key)
1328
1634
  }
1329
- });
1635
+ }, messageDigestCache, rawProofCandidate);
1330
1636
  }
1331
1637
  logger.debug("The fetched key (verificationMethod) for the proof is not a valid Ed25519 key:\n{keyId}", {
1332
1638
  proof,
@@ -1337,15 +1643,21 @@ async function verifyProofInternal(jsonLd, proof, options) {
1337
1643
  const digest = new Uint8Array(proofDigest.byteLength + 32);
1338
1644
  digest.set(new Uint8Array(proofDigest), 0);
1339
1645
  const proofValue = proof.proofValue;
1340
- const verifyCandidate = async (candidate) => {
1341
- const msgBytes = encoder.encode((0, json_canon.default)(candidate));
1342
- const msgDigest = await crypto.subtle.digest("SHA-256", msgBytes);
1646
+ const verifyCandidate = async (msgDigest) => {
1343
1647
  digest.set(new Uint8Array(msgDigest), proofDigest.byteLength);
1344
1648
  return await crypto.subtle.verify("Ed25519", publicKey.publicKey, proofValue.slice(), digest);
1345
1649
  };
1346
- if (await verifyCandidate(msg)) return publicKey;
1347
- const normalized = await normalizeOutgoingActivityJsonLd(msg, preloadedOnlyDocumentLoader);
1348
- if (normalized !== msg && await verifyCandidate(normalized)) return publicKey;
1650
+ const messageDigestKey = (0, json_canon.default)(proofConfiguration.context ?? null);
1651
+ const messageDigestValues = messageDigestCache.values ??= /* @__PURE__ */ new Map();
1652
+ let messageDigestsPromise = messageDigestValues.get(messageDigestKey);
1653
+ if (messageDigestsPromise == null) {
1654
+ messageDigestsPromise = createProofMessageDigests(jsonLd, messageDigestCache.proofContextLoader, proofConfiguration.context);
1655
+ messageDigestValues.set(messageDigestKey, messageDigestsPromise);
1656
+ }
1657
+ const messageDigests = await messageDigestsPromise;
1658
+ if (await verifyCandidate(messageDigests.onWire)) return publicKey;
1659
+ const normalizedDigest = await messageDigests.normalized();
1660
+ if (normalizedDigest != null && await verifyCandidate(normalizedDigest)) return publicKey;
1349
1661
  if (fetchedKey.cached) {
1350
1662
  logger.debug("Failed to verify the proof with the cached key {keyId}; retrying with the freshly fetched key...", {
1351
1663
  keyId: proof.verificationMethodId.href,
@@ -1357,7 +1669,7 @@ async function verifyProofInternal(jsonLd, proof, options) {
1357
1669
  get: () => Promise.resolve(void 0),
1358
1670
  set: async (keyId, key) => await options.keyCache?.set(keyId, key)
1359
1671
  }
1360
- });
1672
+ }, messageDigestCache, rawProofCandidate);
1361
1673
  }
1362
1674
  logger.debug("Failed to verify the proof with the fetched key {keyId}:\n{proof}", {
1363
1675
  keyId: proof.verificationMethodId.href,
@@ -1365,6 +1677,228 @@ async function verifyProofInternal(jsonLd, proof, options) {
1365
1677
  });
1366
1678
  return null;
1367
1679
  }
1680
+ const AS_NAMESPACE = "https://www.w3.org/ns/activitystreams#";
1681
+ const FEP_2277_ACTOR_PROPERTIES = ["http://www.w3.org/ns/ldp#inbox", `${AS_NAMESPACE}outbox`];
1682
+ const FEP_2277_COLLECTION_PROPERTIES = [
1683
+ "items",
1684
+ "orderedItems",
1685
+ "totalItems",
1686
+ "partOf",
1687
+ "first",
1688
+ "last",
1689
+ "next",
1690
+ "prev",
1691
+ "current"
1692
+ ].map((property) => AS_NAMESPACE + property);
1693
+ const PORTABLE_OBJECT_ID_PATTERN = /^ap(?:\+ef61)?:\/\//i;
1694
+ const FUNCTIONAL_PROOF_PROPERTIES = [
1695
+ `${SECURITY_NAMESPACE}cryptosuite`,
1696
+ SECURITY_VERIFICATION_METHOD,
1697
+ `${SECURITY_NAMESPACE}proofPurpose`,
1698
+ `${SECURITY_NAMESPACE}proofValue`,
1699
+ "http://purl.org/dc/terms/created"
1700
+ ];
1701
+ function isJsonLdNode(value) {
1702
+ return typeof value === "object" && value != null && !Array.isArray(value);
1703
+ }
1704
+ function hasValidPortableProofShape(proofValue) {
1705
+ if (!isJsonLdNode(proofValue)) return false;
1706
+ let proofNode = proofValue;
1707
+ if ("@graph" in proofNode) {
1708
+ const graph = proofNode["@graph"];
1709
+ if (!Array.isArray(graph) || graph.length !== 1 || !isJsonLdNode(graph[0])) return false;
1710
+ proofNode = graph[0];
1711
+ }
1712
+ const types = proofNode["@type"];
1713
+ return Array.isArray(types) && types.length === 1 && types[0] === DATA_INTEGRITY_PROOF && FUNCTIONAL_PROOF_PROPERTIES.every((property) => {
1714
+ const values = proofNode[property];
1715
+ return Array.isArray(values) && values.length === 1 && !(isJsonLdNode(values[0]) && "@list" in values[0]);
1716
+ });
1717
+ }
1718
+ function classifyFep2277CoreType(node) {
1719
+ if (FEP_2277_ACTOR_PROPERTIES.every((property) => property in node)) return "actor";
1720
+ if (`${SECURITY_NAMESPACE}publicKeyMultibase` in node) return "verificationMethod";
1721
+ if (`${SECURITY_NAMESPACE}publicKeyPem` in node) return "publicKey";
1722
+ if (`${AS_NAMESPACE}href` in node) return "link";
1723
+ if (`${AS_NAMESPACE}actor` in node) return "activity";
1724
+ if (FEP_2277_COLLECTION_PROPERTIES.some((property) => property in node)) return "collection";
1725
+ return "object";
1726
+ }
1727
+ async function expandPortableObjectRoot(jsonLd, contextLoader) {
1728
+ if (!isJsonLdNode(jsonLd)) throw new TypeError("Expected a single JSON-LD object.");
1729
+ const loadedContexts = /* @__PURE__ */ new Map();
1730
+ const loader = getNormalizationContextLoader(contextLoader);
1731
+ const recordingLoader = async (url, options) => {
1732
+ const remoteDocument = await loader(url, options);
1733
+ const key = URL.canParse(url) ? new URL(url).href : url;
1734
+ loadedContexts.set(key, structuredClone(remoteDocument));
1735
+ return remoteDocument;
1736
+ };
1737
+ const expanded = await _fedify_vocab_runtime_jsonld.default.expand(jsonLd, {
1738
+ documentLoader: recordingLoader,
1739
+ keepFreeFloatingNodes: true
1740
+ });
1741
+ if (expanded.length !== 1 || !isJsonLdNode(expanded[0])) throw new TypeError("Expected a single JSON-LD object.");
1742
+ return {
1743
+ root: expanded[0],
1744
+ proofContextLoader: async (url, options) => {
1745
+ const key = URL.canParse(url) ? new URL(url).href : url;
1746
+ const remoteDocument = loadedContexts.get(key);
1747
+ if (remoteDocument != null) return structuredClone(remoteDocument);
1748
+ return await preloadedOnlyDocumentLoader(url, options);
1749
+ }
1750
+ };
1751
+ }
1752
+ /**
1753
+ * Verifies the FEP-ef61 Object Integrity Proof policy for a portable object.
1754
+ *
1755
+ * This applies the FEP-2277 core-type classification to the top-level JSON-LD
1756
+ * node. Portable actors, activities, and objects require proofs. A portable
1757
+ * collection without a proof is reported separately so a caller can apply a
1758
+ * gateway trust policy. Embedded portable objects are not traversed.
1759
+ *
1760
+ * Every proof must use a DID URL whose DID matches the portable object's
1761
+ * authority, and every proof must pass {@link verifyProof}.
1762
+ *
1763
+ * @param jsonLd The JSON-LD document to verify.
1764
+ * @param options Additional options. See also
1765
+ * {@link VerifyPortableObjectProofOptions}.
1766
+ * @returns The detailed portable proof-policy result.
1767
+ * @throws {TypeError} If the input is not a single JSON-LD object or has a
1768
+ * malformed portable ID.
1769
+ * @since 2.4.0
1770
+ */
1771
+ async function verifyPortableObjectProof(jsonLd, options = {}) {
1772
+ if (isJsonLdNode(jsonLd) && typeof jsonLd["@id"] === "string" && !PORTABLE_OBJECT_ID_PATTERN.test(jsonLd["@id"])) return {
1773
+ verified: false,
1774
+ reason: { type: "notPortableObject" }
1775
+ };
1776
+ const { root, proofContextLoader } = await expandPortableObjectRoot(jsonLd, options.contextLoader);
1777
+ const id = root["@id"];
1778
+ if (typeof id !== "string" || !PORTABLE_OBJECT_ID_PATTERN.test(id)) return {
1779
+ verified: false,
1780
+ reason: { type: "notPortableObject" }
1781
+ };
1782
+ const objectId = (0, _fedify_vocab_runtime.parseIri)(id);
1783
+ (0, _fedify_vocab_runtime.getFe34Origin)(objectId);
1784
+ const objectType = classifyFep2277CoreType(root);
1785
+ if (objectType === "verificationMethod" || objectType === "publicKey" || objectType === "link") return {
1786
+ verified: false,
1787
+ reason: {
1788
+ type: "unsupportedObjectType",
1789
+ objectType
1790
+ }
1791
+ };
1792
+ const proofValues = root[SECURITY_PROOF];
1793
+ if (proofValues == null || Array.isArray(proofValues) && proofValues.length < 1) return objectType === "collection" ? {
1794
+ verified: false,
1795
+ reason: { type: "unsecuredCollection" }
1796
+ } : {
1797
+ verified: false,
1798
+ reason: { type: "missingProof" }
1799
+ };
1800
+ if (!Array.isArray(proofValues)) return {
1801
+ verified: false,
1802
+ reason: {
1803
+ type: "invalidProof",
1804
+ proofIndex: 0
1805
+ }
1806
+ };
1807
+ const rawProofValues = isJsonLdNode(jsonLd) ? await getRawProofValues(jsonLd, proofContextLoader) : [];
1808
+ const proofs = [];
1809
+ for (let proofIndex = 0; proofIndex < proofValues.length; proofIndex++) {
1810
+ const proofValue = proofValues[proofIndex];
1811
+ if (!hasValidPortableProofShape(proofValue)) return {
1812
+ verified: false,
1813
+ reason: {
1814
+ type: "invalidProof",
1815
+ proofIndex
1816
+ }
1817
+ };
1818
+ let proof;
1819
+ try {
1820
+ proof = await _fedify_vocab.DataIntegrityProof.fromJsonLd(proofValue, options);
1821
+ } catch {
1822
+ return {
1823
+ verified: false,
1824
+ reason: {
1825
+ type: "invalidProof",
1826
+ proofIndex
1827
+ }
1828
+ };
1829
+ }
1830
+ proofs.push(proof);
1831
+ }
1832
+ for (let proofIndex = 0; proofIndex < proofs.length; proofIndex++) {
1833
+ const verificationMethod = proofs[proofIndex].verificationMethodId;
1834
+ if (verificationMethod == null) return {
1835
+ verified: false,
1836
+ reason: {
1837
+ type: "invalidProof",
1838
+ proofIndex
1839
+ }
1840
+ };
1841
+ if (verificationMethod.protocol !== "did:") return {
1842
+ verified: false,
1843
+ reason: {
1844
+ type: "unsupportedVerificationMethod",
1845
+ proofIndex,
1846
+ verificationMethod
1847
+ }
1848
+ };
1849
+ try {
1850
+ (0, _fedify_vocab_runtime.getFe34Origin)(verificationMethod);
1851
+ } catch (error) {
1852
+ if (!(error instanceof TypeError)) throw error;
1853
+ return {
1854
+ verified: false,
1855
+ reason: {
1856
+ type: "unsupportedVerificationMethod",
1857
+ proofIndex,
1858
+ verificationMethod
1859
+ }
1860
+ };
1861
+ }
1862
+ if (!(0, _fedify_vocab_runtime.haveSameFe34Origin)(objectId, verificationMethod)) return {
1863
+ verified: false,
1864
+ reason: {
1865
+ type: "verificationMethodMismatch",
1866
+ proofIndex,
1867
+ objectId,
1868
+ verificationMethod
1869
+ }
1870
+ };
1871
+ }
1872
+ const keys = [];
1873
+ const rawProofCandidatePool = {
1874
+ candidates: await parseRawProofCandidates(jsonLd, rawProofValues, options, proofContextLoader),
1875
+ used: /* @__PURE__ */ new Set()
1876
+ };
1877
+ const messageDigestCache = { proofContextLoader };
1878
+ for (let proofIndex = 0; proofIndex < proofs.length; proofIndex++) {
1879
+ const rawProofCandidate = takeRawProofCandidate(rawProofCandidatePool, proofs[proofIndex]);
1880
+ if (rawProofCandidate == null) return {
1881
+ verified: false,
1882
+ reason: {
1883
+ type: "invalidProof",
1884
+ proofIndex
1885
+ }
1886
+ };
1887
+ const key = await verifyProofWithMessageDigestCache(jsonLd, proofs[proofIndex], options, messageDigestCache, rawProofCandidate);
1888
+ if (key == null) return {
1889
+ verified: false,
1890
+ reason: {
1891
+ type: "invalidProof",
1892
+ proofIndex
1893
+ }
1894
+ };
1895
+ keys.push(key);
1896
+ }
1897
+ return {
1898
+ verified: true,
1899
+ keys
1900
+ };
1901
+ }
1368
1902
  /**
1369
1903
  * Verifies the given object. It will verify all the proofs in the object,
1370
1904
  * and succeed only if all the proofs are valid and all attributions and
@@ -1386,10 +1920,48 @@ async function verifyObject(cls, jsonLd, options = {}) {
1386
1920
  "proof"
1387
1921
  ]);
1388
1922
  const object = await cls.fromJsonLd(jsonLd, options);
1923
+ const defaultDocumentLoader = (0, _fedify_vocab_runtime.getDocumentLoader)();
1924
+ const proofContextLoader = options.contextLoader ?? defaultDocumentLoader;
1389
1925
  const attributions = new Set(object.attributionIds.map((uri) => uri.href));
1390
1926
  if (object instanceof _fedify_vocab.Activity) for (const uri of object.actorIds) attributions.add(uri.href);
1391
- for await (const proof of object.getProofs(options)) {
1392
- const key = await verifyProof(jsonLd, proof, options);
1927
+ const rawProofValues = isJsonLdNode(jsonLd) ? await getRawProofValues(jsonLd, proofContextLoader) : [];
1928
+ const rawProofCandidates = isJsonLdNode(jsonLd) ? await parseRawProofCandidates(jsonLd, rawProofValues, options, proofContextLoader) : [];
1929
+ const rawProofCandidatePool = {
1930
+ candidates: rawProofCandidates,
1931
+ used: /* @__PURE__ */ new Set()
1932
+ };
1933
+ const baseDocumentLoader = options.documentLoader ?? defaultDocumentLoader;
1934
+ const hydratedCandidates = /* @__PURE__ */ new Set();
1935
+ const proofDocumentLoader = async (url, loaderOptions) => {
1936
+ const remoteDocument = await baseDocumentLoader(url, loaderOptions);
1937
+ const reference = normalizeDocumentUrl(url);
1938
+ const candidateIndex = rawProofCandidates.findIndex((candidate, index) => !hydratedCandidates.has(index) && !rawProofCandidatePool.used.has(index) && candidate.reference === reference);
1939
+ if (candidateIndex >= 0) {
1940
+ hydratedCandidates.add(candidateIndex);
1941
+ let parsed = null;
1942
+ try {
1943
+ parsed = await _fedify_vocab.DataIntegrityProof.fromJsonLd(remoteDocument.document, {
1944
+ documentLoader: baseDocumentLoader,
1945
+ contextLoader: proofContextLoader,
1946
+ tracerProvider: options.tracerProvider,
1947
+ baseUrl: (0, _fedify_vocab_runtime.parseIri)(remoteDocument.documentUrl)
1948
+ });
1949
+ } catch {}
1950
+ rawProofCandidates[candidateIndex] = {
1951
+ value: structuredClone(remoteDocument.document),
1952
+ proof: parsed,
1953
+ reference
1954
+ };
1955
+ }
1956
+ return remoteDocument;
1957
+ };
1958
+ for await (const proof of object.getProofs({
1959
+ ...options,
1960
+ documentLoader: proofDocumentLoader
1961
+ })) {
1962
+ const rawProofCandidate = takeRawProofCandidate(rawProofCandidatePool, proof);
1963
+ if (rawProofCandidate == null) return null;
1964
+ const key = await verifyProofWithMessageDigestCache(jsonLd, proof, options, { proofContextLoader }, rawProofCandidate);
1393
1965
  if (key === null) return null;
1394
1966
  if (proof.verificationMethodId == null) return null;
1395
1967
  if (key.controllerId == null) {
@@ -1541,6 +2113,12 @@ Object.defineProperty(exports, "verifyObject", {
1541
2113
  return verifyObject;
1542
2114
  }
1543
2115
  });
2116
+ Object.defineProperty(exports, "verifyPortableObjectProof", {
2117
+ enumerable: true,
2118
+ get: function() {
2119
+ return verifyPortableObjectProof;
2120
+ }
2121
+ });
1544
2122
  Object.defineProperty(exports, "verifyProof", {
1545
2123
  enumerable: true,
1546
2124
  get: function() {