@fedify/fedify 2.4.3 → 2.4.5-dev.2442

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 (83) hide show
  1. package/dist/{builder-CztvQYX0.mjs → builder-CfWRet8P.mjs} +2 -2
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{compound-proof-rcvB6i5j.mjs → compound-proof-DhRmQh3O.mjs} +2 -2
  4. package/dist/{deno-BEgaR4dC.mjs → deno-Cmfa0sOL.mjs} +1 -1
  5. package/dist/{docloader-Wf9RroxL.mjs → docloader-XwuoivPh.mjs} +5 -3
  6. package/dist/federation/builder.test.mjs +1 -1
  7. package/dist/federation/compound-fanout.test.mjs +3 -3
  8. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  9. package/dist/federation/handler.test.mjs +4 -4
  10. package/dist/federation/hashlink-media.test.mjs +2 -2
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/inbox-report.test.mjs +5 -5
  13. package/dist/federation/inbox.test.mjs +1 -1
  14. package/dist/federation/keycache.test.mjs +20 -9
  15. package/dist/federation/metrics.test.mjs +1 -1
  16. package/dist/federation/middleware.test.mjs +8 -8
  17. package/dist/federation/mod.cjs +1 -1
  18. package/dist/federation/mod.js +1 -1
  19. package/dist/federation/outgoing-proof.test.mjs +4 -4
  20. package/dist/federation/portable-actor.test.mjs +3 -3
  21. package/dist/federation/portable-chain.test.mjs +3 -3
  22. package/dist/federation/portable-collection.test.mjs +4 -4
  23. package/dist/federation/portable-delivery.test.mjs +2 -2
  24. package/dist/federation/portable-gateway-keys.test.mjs +5 -5
  25. package/dist/federation/portable-inbox.test.mjs +4 -4
  26. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  27. package/dist/federation/portable-route-activity.test.mjs +2 -2
  28. package/dist/federation/portable.test.mjs +3 -3
  29. package/dist/federation/send.test.mjs +3 -3
  30. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  31. package/dist/federation/tasks/tasks.test.mjs +2 -2
  32. package/dist/federation/temporal.test.mjs +1 -1
  33. package/dist/federation/webfinger.test.mjs +1 -1
  34. package/dist/{federation-O8HwbuUv.cjs → federation-ckG1Y8Mq.cjs} +4 -4
  35. package/dist/{http-BFC8b7mH.cjs → http-DwiAt6qm.cjs} +17 -3
  36. package/dist/{http-B5pRXkmQ.js → http-Eia1HcEX.js} +17 -3
  37. package/dist/{http-CQcqhcga.mjs → http-wf5LuVhT.mjs} +4 -4
  38. package/dist/{inbox-CsErRN6L.mjs → inbox-CKaG1WuG.mjs} +2 -2
  39. package/dist/{keycache-D-MNltOk.mjs → keycache-N2opu4-3.mjs} +1 -1
  40. package/dist/{kv-cache-4oZXC3pf.mjs → kv-cache-BJU6nOS6.mjs} +1 -1
  41. package/dist/{kv-cache-JOKAStz2.cjs → kv-cache-BOmfB5jl.cjs} +4 -2
  42. package/dist/{kv-cache-Bih-8AoZ.js → kv-cache-Dp8iech6.js} +4 -2
  43. package/dist/{ld-Qk4WqslS.mjs → ld-DjK2r7Tq.mjs} +18 -4
  44. package/dist/{metrics-DAhJVtgg.mjs → metrics-Czeslsim.mjs} +1 -1
  45. package/dist/{middleware-KeANQWIR.js → middleware-B6P74YrH.js} +4 -4
  46. package/dist/{middleware-C76AHpbO.mjs → middleware-CQTSLIng.mjs} +1 -1
  47. package/dist/{middleware-B582s7UO.mjs → middleware-VMeVcza7.mjs} +15 -15
  48. package/dist/mod.cjs +4 -4
  49. package/dist/mod.js +4 -4
  50. package/dist/nodeinfo/handler.test.mjs +1 -1
  51. package/dist/{owner-CcyHGdSK.mjs → owner-MYmQvfAW.mjs} +3 -3
  52. package/dist/{portable-collection-Chb6A43p.js → portable-collection-DOJ_TENm.js} +1 -1
  53. package/dist/{portable-collection-0RzCsflU.mjs → portable-collection-DQnT0cZ4.mjs} +2 -2
  54. package/dist/{portable-collection-BHZq0F_c.cjs → portable-collection-Dqycbd4w.cjs} +1 -1
  55. package/dist/{portable-key-BY6G_X28.mjs → portable-key-BCK9qPc6.mjs} +1 -1
  56. package/dist/{proof-nIOXkRme.mjs → proof-bC_S5Pka.mjs} +3 -3
  57. package/dist/{send-C2r9whkz.mjs → send-tyC1hLbH.mjs} +3 -3
  58. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  59. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  60. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  61. package/dist/sig/compound-proof.test.mjs +2 -2
  62. package/dist/sig/http.test.mjs +3 -3
  63. package/dist/sig/key.test.mjs +196 -1
  64. package/dist/sig/ld.test.mjs +46 -1
  65. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  66. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  67. package/dist/sig/mod.cjs +2 -2
  68. package/dist/sig/mod.js +2 -2
  69. package/dist/sig/owner.test.mjs +46 -1
  70. package/dist/sig/portable-collection.test.mjs +1 -1
  71. package/dist/sig/portable-interop.test.mjs +3 -3
  72. package/dist/sig/portable-key.test.mjs +6 -6
  73. package/dist/sig/proof.test.mjs +54 -3
  74. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  75. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  76. package/dist/sig/verification.test.mjs +3 -3
  77. package/dist/{temporal-D5tHfTGO.mjs → temporal-BaGwCXi9.mjs} +1 -1
  78. package/dist/testing/kv.test.mjs +1 -1
  79. package/dist/utils/docloader.test.mjs +66 -2
  80. package/dist/utils/kv-cache.test.mjs +1 -1
  81. package/dist/utils/mod.cjs +1 -1
  82. package/dist/utils/mod.js +1 -1
  83. package/package.json +7 -7
@@ -2,12 +2,13 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
5
+ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
5
6
  import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
6
7
  import "../std__assert-DvMDRPes.mjs";
7
8
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
8
9
  import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
9
10
  import { t as assert } from "../assert-D1Eafmd_.mjs";
10
- import { C as generateCryptoKeyPair, S as fetchKeyDetailed, T as validateCryptoKey, _ as wrapContextLoaderForJsonLd, w as importJwk, x as fetchKey, y as exportJwk } from "../ld-Qk4WqslS.mjs";
11
+ import { C as generateCryptoKeyPair, S as fetchKeyDetailed, T as validateCryptoKey, _ as wrapContextLoaderForJsonLd, w as importJwk, x as fetchKey, y as exportJwk } from "../ld-DjK2r7Tq.mjs";
11
12
  import { c as rsaPublicKey3, i as rsaPrivateKey2, o as rsaPublicKey1, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
12
13
  import { CryptographicKey, Multikey, Person } from "@fedify/vocab";
13
14
  import { FetchError, UrlError, exportDidKey, getDocumentLoader } from "@fedify/vocab-runtime";
@@ -1051,5 +1052,199 @@ test("fetchKey() records the controller an actor document establishes", async ()
1051
1052
  assertEquals(key?.controllerId, new URL(actorId));
1052
1053
  assert(key?.publicKey != null);
1053
1054
  });
1055
+ test("fetchKey() rejects a key whose owner redirects to another actor", async () => {
1056
+ const keyId = "https://evil.example/keys/1";
1057
+ const victim = "https://victim.example/users/alice";
1058
+ const mallory = "https://evil.example/users/mallory";
1059
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
1060
+ const cache = {};
1061
+ assertEquals(await fetchKey(keyId, CryptographicKey, {
1062
+ documentLoader(resource) {
1063
+ if (resource === keyId) return Promise.resolve({
1064
+ contextUrl: null,
1065
+ documentUrl: resource,
1066
+ document: {
1067
+ "@context": "https://w3id.org/security/v1",
1068
+ id: keyId,
1069
+ type: "CryptographicKey",
1070
+ owner: victim,
1071
+ publicKeyPem
1072
+ }
1073
+ });
1074
+ if (resource === victim || resource === mallory) return Promise.resolve({
1075
+ contextUrl: null,
1076
+ documentUrl: mallory,
1077
+ document: {
1078
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
1079
+ id: mallory,
1080
+ type: "Person",
1081
+ publicKey: keyId
1082
+ }
1083
+ });
1084
+ return mockDocumentLoader(resource);
1085
+ },
1086
+ contextLoader: mockDocumentLoader,
1087
+ keyCache: {
1088
+ get(keyId) {
1089
+ return Promise.resolve(cache[keyId.href]);
1090
+ },
1091
+ set(keyId, key) {
1092
+ cache[keyId.href] = key;
1093
+ return Promise.resolve();
1094
+ }
1095
+ }
1096
+ }), {
1097
+ key: null,
1098
+ cached: false
1099
+ });
1100
+ assertEquals(cache, { [keyId]: null });
1101
+ });
1102
+ test("fetchKey() rejects a key whose owner resolves to another actor on the same origin", async () => {
1103
+ const keyId = "https://victim.example/keys/bob";
1104
+ const victim = "https://victim.example/users/alice";
1105
+ const bob = "https://victim.example/users/bob";
1106
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
1107
+ assertEquals(await fetchKey(keyId, CryptographicKey, {
1108
+ documentLoader(resource) {
1109
+ if (resource === keyId) return Promise.resolve({
1110
+ contextUrl: null,
1111
+ documentUrl: resource,
1112
+ document: {
1113
+ "@context": "https://w3id.org/security/v1",
1114
+ id: keyId,
1115
+ type: "CryptographicKey",
1116
+ owner: victim,
1117
+ publicKeyPem
1118
+ }
1119
+ });
1120
+ if (resource === victim || resource === bob) return Promise.resolve({
1121
+ contextUrl: null,
1122
+ documentUrl: resource,
1123
+ document: {
1124
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
1125
+ id: bob,
1126
+ type: "Person",
1127
+ publicKey: keyId
1128
+ }
1129
+ });
1130
+ return mockDocumentLoader(resource);
1131
+ },
1132
+ contextLoader: mockDocumentLoader
1133
+ }), {
1134
+ key: null,
1135
+ cached: false
1136
+ });
1137
+ });
1138
+ test("fetchKey() rejects a Multikey whose controller redirects to another actor", async () => {
1139
+ const keyId = "https://evil.example/keys/ed25519";
1140
+ const victim = "https://victim.example/users/alice";
1141
+ const mallory = "https://evil.example/users/mallory";
1142
+ const multikeyDocument = await ed25519Multikey.toJsonLd({ contextLoader: mockDocumentLoader });
1143
+ assertEquals(await fetchKey(keyId, Multikey, {
1144
+ documentLoader(resource) {
1145
+ if (resource === keyId) return Promise.resolve({
1146
+ contextUrl: null,
1147
+ documentUrl: resource,
1148
+ document: {
1149
+ ...multikeyDocument,
1150
+ id: keyId,
1151
+ controller: victim
1152
+ }
1153
+ });
1154
+ if (resource === victim || resource === mallory) return Promise.resolve({
1155
+ contextUrl: null,
1156
+ documentUrl: mallory,
1157
+ document: {
1158
+ "@context": [
1159
+ "https://www.w3.org/ns/activitystreams",
1160
+ "https://w3id.org/security/v1",
1161
+ "https://w3id.org/security/multikey/v1",
1162
+ "https://w3id.org/security/data-integrity/v1",
1163
+ "https://www.w3.org/ns/did/v1"
1164
+ ],
1165
+ id: mallory,
1166
+ type: "Person",
1167
+ assertionMethod: [keyId]
1168
+ }
1169
+ });
1170
+ return mockDocumentLoader(resource);
1171
+ },
1172
+ contextLoader: mockDocumentLoader
1173
+ }), {
1174
+ key: null,
1175
+ cached: false
1176
+ });
1177
+ });
1178
+ test("fetchKey() accepts an owner whose document URL differs from its id", async () => {
1179
+ const keyId = "https://victim.example/keys/alice";
1180
+ const alice = "https://victim.example/users/alice";
1181
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
1182
+ const { key, cached } = await fetchKey(keyId, CryptographicKey, {
1183
+ documentLoader(resource) {
1184
+ if (resource === keyId) return Promise.resolve({
1185
+ contextUrl: null,
1186
+ documentUrl: resource,
1187
+ document: {
1188
+ "@context": "https://w3id.org/security/v1",
1189
+ id: keyId,
1190
+ type: "CryptographicKey",
1191
+ owner: alice,
1192
+ publicKeyPem
1193
+ }
1194
+ });
1195
+ if (resource === alice) return Promise.resolve({
1196
+ contextUrl: null,
1197
+ documentUrl: `${alice}.json`,
1198
+ document: {
1199
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
1200
+ id: alice,
1201
+ type: "Person",
1202
+ publicKey: keyId
1203
+ }
1204
+ });
1205
+ return mockDocumentLoader(resource);
1206
+ },
1207
+ contextLoader: mockDocumentLoader
1208
+ });
1209
+ assertFalse(cached);
1210
+ assertEquals(key?.id, new URL(keyId));
1211
+ assertEquals(key?.ownerId, new URL(alice));
1212
+ });
1213
+ test("fetchKey() rejects an owner reached through a normalization redirect", async () => {
1214
+ const keyId = "https://victim.example/keys/alice";
1215
+ const canonical = "https://victim.example/users/alice";
1216
+ const nonCanonical = "https://victim.example/users/Alice";
1217
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
1218
+ assertEquals(await fetchKey(keyId, CryptographicKey, {
1219
+ documentLoader(resource) {
1220
+ if (resource === keyId) return Promise.resolve({
1221
+ contextUrl: null,
1222
+ documentUrl: resource,
1223
+ document: {
1224
+ "@context": "https://w3id.org/security/v1",
1225
+ id: keyId,
1226
+ type: "CryptographicKey",
1227
+ owner: nonCanonical,
1228
+ publicKeyPem
1229
+ }
1230
+ });
1231
+ if (resource === canonical || resource === nonCanonical) return Promise.resolve({
1232
+ contextUrl: null,
1233
+ documentUrl: canonical,
1234
+ document: {
1235
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
1236
+ id: canonical,
1237
+ type: "Person",
1238
+ publicKey: keyId
1239
+ }
1240
+ });
1241
+ return mockDocumentLoader(resource);
1242
+ },
1243
+ contextLoader: mockDocumentLoader
1244
+ }), {
1245
+ key: null,
1246
+ cached: false
1247
+ });
1248
+ });
1054
1249
  //#endregion
1055
1250
  export {};
@@ -8,7 +8,7 @@ import { t as assertInstanceOf } from "../assert_instance_of-mrXWWNY-.mjs";
8
8
  import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
9
9
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
10
10
  import { t as assert } from "../assert-D1Eafmd_.mjs";
11
- import { C as generateCryptoKeyPair, a as compactJsonLd, f as isInvalidUrlTypeError, g as verifySignature, h as verifyJsonLd, i as attachSignature, n as UnsafeJsonLdError, o as createSignature, p as signJsonLd, s as detachSignature, u as hasSignatureLike } from "../ld-Qk4WqslS.mjs";
11
+ import { C as generateCryptoKeyPair, a as compactJsonLd, f as isInvalidUrlTypeError, g as verifySignature, h as verifyJsonLd, i as attachSignature, n as UnsafeJsonLdError, o as createSignature, p as signJsonLd, s as detachSignature, u as hasSignatureLike } from "../ld-DjK2r7Tq.mjs";
12
12
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
13
13
  import { CryptographicKey } from "@fedify/vocab";
14
14
  import { createTestMeterProvider, mockDocumentLoader, test } from "@fedify/fixture";
@@ -995,5 +995,50 @@ test("verifyJsonLd() rejects a key that claims a forged owner", async () => {
995
995
  contextLoader: mockDocumentLoader
996
996
  }));
997
997
  });
998
+ test("verifyJsonLd() rejects a key whose owner redirects to another actor", async () => {
999
+ const keyId = new URL("https://evil.example/keys/1");
1000
+ const victim = "https://victim.example/users/alice";
1001
+ const mallory = "https://evil.example/users/mallory";
1002
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
1003
+ const signed = await signJsonLd({
1004
+ "@context": "https://www.w3.org/ns/activitystreams",
1005
+ id: "https://victim.example/activities/forged",
1006
+ type: "Create",
1007
+ actor: victim,
1008
+ object: {
1009
+ type: "Note",
1010
+ content: "forged"
1011
+ }
1012
+ }, rsaPrivateKey3, keyId, { contextLoader: mockDocumentLoader });
1013
+ const options = {
1014
+ documentLoader(resource) {
1015
+ if (resource === keyId.href) return Promise.resolve({
1016
+ contextUrl: null,
1017
+ documentUrl: resource,
1018
+ document: {
1019
+ "@context": "https://w3id.org/security/v1",
1020
+ id: keyId.href,
1021
+ type: "CryptographicKey",
1022
+ owner: victim,
1023
+ publicKeyPem
1024
+ }
1025
+ });
1026
+ if (resource === victim || resource === mallory) return Promise.resolve({
1027
+ contextUrl: null,
1028
+ documentUrl: mallory,
1029
+ document: {
1030
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
1031
+ id: mallory,
1032
+ type: "Person",
1033
+ publicKey: keyId.href
1034
+ }
1035
+ });
1036
+ return mockDocumentLoader(resource);
1037
+ },
1038
+ contextLoader: mockDocumentLoader
1039
+ };
1040
+ assertEquals(await verifySignature(signed, options), null);
1041
+ assertFalse(await verifyJsonLd(signed, options));
1042
+ });
998
1043
  //#endregion
999
1044
  export {};
@@ -5,10 +5,10 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
6
  import "../std__assert-DvMDRPes.mjs";
7
7
  import { t as assert } from "../assert-D1Eafmd_.mjs";
8
- import { c as verifyPortableObjectProof } from "../proof-nIOXkRme.mjs";
9
- import { p as verifyRequestDetailed } from "../http-CQcqhcga.mjs";
10
- import { o as createFederation } from "../middleware-B582s7UO.mjs";
11
- import { t as doesActorOwnKey } from "../owner-CcyHGdSK.mjs";
8
+ import { c as verifyPortableObjectProof } from "../proof-bC_S5Pka.mjs";
9
+ import { p as verifyRequestDetailed } from "../http-wf5LuVhT.mjs";
10
+ import { o as createFederation } from "../middleware-VMeVcza7.mjs";
11
+ import { t as doesActorOwnKey } from "../owner-MYmQvfAW.mjs";
12
12
  import { Create, Person } from "@fedify/vocab";
13
13
  import { canonicalizePortableUri, decodeMultibase, importDidKey } from "@fedify/vocab-runtime";
14
14
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -4,9 +4,9 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
5
5
  import "../std__assert-DvMDRPes.mjs";
6
6
  import { t as assert } from "../assert-D1Eafmd_.mjs";
7
- import { c as verifyPortableObjectProof } from "../proof-nIOXkRme.mjs";
8
- import { p as verifyRequestDetailed } from "../http-CQcqhcga.mjs";
9
- import { t as doesActorOwnKey } from "../owner-CcyHGdSK.mjs";
7
+ import { c as verifyPortableObjectProof } from "../proof-bC_S5Pka.mjs";
8
+ import { p as verifyRequestDetailed } from "../http-wf5LuVhT.mjs";
9
+ import { t as doesActorOwnKey } from "../owner-MYmQvfAW.mjs";
10
10
  import { Create } from "@fedify/vocab";
11
11
  import { canonicalizePortableUri, decodeMultibase, fromCompatibleEf61Id, importDidKey } from "@fedify/vocab-runtime";
12
12
  import { mockDocumentLoader, test } from "@fedify/fixture";
package/dist/sig/mod.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_http = require("../http-BFC8b7mH.cjs");
4
- const require_portable_collection = require("../portable-collection-BHZq0F_c.cjs");
3
+ const require_http = require("../http-DwiAt6qm.cjs");
4
+ const require_portable_collection = require("../portable-collection-Dqycbd4w.cjs");
5
5
  exports.attachSignature = require_http.attachSignature;
6
6
  exports.createProof = require_http.createProof;
7
7
  exports.createSignature = require_http.createSignature;
package/dist/sig/mod.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { D as createSignature, H as fetchKeyDetailed, I as verifyJsonLd, L as verifySignature, O as detachSignature, P as signJsonLd, T as attachSignature, U as generateCryptoKeyPair, V as fetchKey, W as importJwk, _ as verifyObject, _t as validateAcceptSignature, a as signRequest, c as verifyRequestDetailed, gt as parseAcceptSignature, h as signObject, ht as fulfillAcceptSignature, j as hasSignatureLike, m as hasProofLike, mt as formatAcceptSignature, p as createProof, s as verifyRequest, v as verifyPortableObjectProof, x as verifyProof, z as exportJwk } from "../http-B5pRXkmQ.js";
4
- import { n as doesActorOwnKey, r as getKeyOwner, t as verifyPortableObject } from "../portable-collection-Chb6A43p.js";
3
+ import { D as createSignature, H as fetchKeyDetailed, I as verifyJsonLd, L as verifySignature, O as detachSignature, P as signJsonLd, T as attachSignature, U as generateCryptoKeyPair, V as fetchKey, W as importJwk, _ as verifyObject, _t as validateAcceptSignature, a as signRequest, c as verifyRequestDetailed, gt as parseAcceptSignature, h as signObject, ht as fulfillAcceptSignature, j as hasSignatureLike, m as hasProofLike, mt as formatAcceptSignature, p as createProof, s as verifyRequest, v as verifyPortableObjectProof, x as verifyProof, z as exportJwk } from "../http-Eia1HcEX.js";
4
+ import { n as doesActorOwnKey, r as getKeyOwner, t as verifyPortableObject } from "../portable-collection-DOJ_TENm.js";
5
5
  export { attachSignature, createProof, createSignature, detachSignature, doesActorOwnKey, exportJwk, fetchKey, fetchKeyDetailed, formatAcceptSignature, fulfillAcceptSignature, generateCryptoKeyPair, getKeyOwner, hasProofLike, hasSignatureLike, importJwk, parseAcceptSignature, signJsonLd, signObject, signRequest, validateAcceptSignature, verifyJsonLd, verifyObject, verifyPortableObject, verifyPortableObjectProof, verifyProof, verifyRequest, verifyRequestDetailed, verifySignature };
@@ -6,7 +6,7 @@ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
6
6
  import "../std__assert-DvMDRPes.mjs";
7
7
  import { t as assert } from "../assert-D1Eafmd_.mjs";
8
8
  import { o as rsaPublicKey1, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
9
- import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-CcyHGdSK.mjs";
9
+ import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-MYmQvfAW.mjs";
10
10
  import { Create, CryptographicKey, lookupObject } from "@fedify/vocab";
11
11
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
12
12
  //#region src/sig/owner.test.ts
@@ -153,5 +153,50 @@ test("doesActorOwnKey() matches a key that leaves its id implicit", async () =>
153
153
  publicKey: rsaPublicKey2.publicKey
154
154
  }), options));
155
155
  });
156
+ test("doesActorOwnKey() rejects an actor ID that redirects to the key's owner", async () => {
157
+ const keyId = "https://evil.example/keys/1";
158
+ const victim = "https://victim.example/users/alice";
159
+ const mallory = "https://evil.example/users/mallory";
160
+ const { publicKeyPem } = await rsaPublicKey1.toJsonLd({ contextLoader: mockDocumentLoader });
161
+ const serveKeyOwnedBy = (owner) => ({
162
+ documentLoader(resource) {
163
+ if (resource === keyId) return Promise.resolve({
164
+ contextUrl: null,
165
+ documentUrl: resource,
166
+ document: {
167
+ "@context": "https://w3id.org/security/v1",
168
+ id: keyId,
169
+ type: "CryptographicKey",
170
+ owner,
171
+ publicKeyPem
172
+ }
173
+ });
174
+ if (resource === victim || resource === mallory) return Promise.resolve({
175
+ contextUrl: null,
176
+ documentUrl: mallory,
177
+ document: {
178
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
179
+ id: mallory,
180
+ type: "Person",
181
+ publicKey: keyId
182
+ }
183
+ });
184
+ return mockDocumentLoader(resource);
185
+ },
186
+ contextLoader: mockDocumentLoader
187
+ });
188
+ const activity = new Create({ actor: new URL(victim) });
189
+ assertFalse(await doesActorOwnKey(activity, new CryptographicKey({
190
+ id: new URL(keyId),
191
+ owner: new URL(mallory),
192
+ publicKey: rsaPublicKey1.publicKey
193
+ }), serveKeyOwnedBy(mallory)));
194
+ assertFalse(await doesActorOwnKey(activity, new CryptographicKey({
195
+ id: new URL(keyId),
196
+ owner: new URL(victim),
197
+ publicKey: rsaPublicKey1.publicKey
198
+ }), serveKeyOwnedBy(victim)));
199
+ assertEquals(await getKeyOwner(new URL(keyId), serveKeyOwnedBy(victim)), null);
200
+ });
156
201
  //#endregion
157
202
  export {};
@@ -2,7 +2,7 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as ed25519PrivateKey, r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
5
- import { t as verifyPortableObject } from "../portable-collection-0RzCsflU.mjs";
5
+ import { t as verifyPortableObject } from "../portable-collection-DQnT0cZ4.mjs";
6
6
  import { Collection, CollectionPage, Note, OrderedCollection, Person, traverseCollection } from "@fedify/vocab";
7
7
  import { FetchError, encodeMultibase, exportDidKey, preloadedContexts } from "@fedify/vocab-runtime";
8
8
  import { test } from "@fedify/fixture";
@@ -4,9 +4,9 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
5
5
  import "../std__assert-DvMDRPes.mjs";
6
6
  import { t as assert } from "../assert-D1Eafmd_.mjs";
7
- import { c as verifyPortableObjectProof, d as verifyProof } from "../proof-nIOXkRme.mjs";
8
- import { n as getKeyOwner } from "../owner-CcyHGdSK.mjs";
9
- import { t as verifyPortableObject } from "../portable-collection-0RzCsflU.mjs";
7
+ import { c as verifyPortableObjectProof, d as verifyProof } from "../proof-bC_S5Pka.mjs";
8
+ import { n as getKeyOwner } from "../owner-MYmQvfAW.mjs";
9
+ import { t as verifyPortableObject } from "../portable-collection-DQnT0cZ4.mjs";
10
10
  import { Application, DataIntegrityProof, lookupObject } from "@fedify/vocab";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  //#region test-vectors/fep-ef61/tootik-v0.25.4/actor.json
@@ -2,13 +2,13 @@ import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
- import { S as fetchKeyDetailed, h as verifyJsonLd, p as signJsonLd, x as fetchKey } from "../ld-Qk4WqslS.mjs";
6
- import { d as verifyProof, r as createProof } from "../proof-nIOXkRme.mjs";
7
- import { n as resolvePortableActorKey } from "../portable-key-BY6G_X28.mjs";
8
- import { f as verifyRequest, l as signRequest, p as verifyRequestDetailed } from "../http-CQcqhcga.mjs";
5
+ import { S as fetchKeyDetailed, h as verifyJsonLd, p as signJsonLd, x as fetchKey } from "../ld-DjK2r7Tq.mjs";
6
+ import { d as verifyProof, r as createProof } from "../proof-bC_S5Pka.mjs";
7
+ import { n as resolvePortableActorKey } from "../portable-key-BCK9qPc6.mjs";
8
+ import { f as verifyRequest, l as signRequest, p as verifyRequestDetailed } from "../http-wf5LuVhT.mjs";
9
9
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
10
- import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-CcyHGdSK.mjs";
11
- import { t as KvKeyCache } from "../keycache-D-MNltOk.mjs";
10
+ import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-MYmQvfAW.mjs";
11
+ import { t as KvKeyCache } from "../keycache-N2opu4-3.mjs";
12
12
  import { Create, CryptographicKey, Multikey, Note, Person } from "@fedify/vocab";
13
13
  import { FetchError, encodeMultibase, exportDidKey, exportSpki, parseIri, preloadedContexts } from "@fedify/vocab-runtime";
14
14
  import { test } from "@fedify/fixture";
@@ -11,10 +11,10 @@ import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
11
11
  import { t as assert } from "../assert-D1Eafmd_.mjs";
12
12
  import { a as verificationObservation } from "../verification-Dpfcw8ml.mjs";
13
13
  import { r as normalizeOutgoingActivityJsonLd } from "../outgoing-jsonld-4IJqPYos.mjs";
14
- import { a as signObject, c as verifyPortableObjectProof, d as verifyProof, i as hasProofLike, o as verifyMapLocalProof, r as createProof, s as verifyObject } from "../proof-nIOXkRme.mjs";
15
- import { f as verifyRequest } from "../http-CQcqhcga.mjs";
14
+ import { a as signObject, c as verifyPortableObjectProof, d as verifyProof, i as hasProofLike, o as verifyMapLocalProof, r as createProof, s as verifyObject } from "../proof-bC_S5Pka.mjs";
15
+ import { f as verifyRequest } from "../http-wf5LuVhT.mjs";
16
16
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
17
- import { t as getAuthenticatedDocumentLoader } from "../docloader-Wf9RroxL.mjs";
17
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-XwuoivPh.mjs";
18
18
  import { Create, DataIntegrityProof, Document, Multikey, Note, PUBLIC_COLLECTION, Place, Translation } from "@fedify/vocab";
19
19
  import { decodeMultibase, encodeMultibase, exportDidKey, exportMultibaseKey, formatIri, importMultibaseKey, parseIri } from "@fedify/vocab-runtime";
20
20
  import { createTestMeterProvider, mockDocumentLoader, test } from "@fedify/fixture";
@@ -2464,5 +2464,56 @@ test("getAuthenticatedDocumentLoader() signs portable object requests to gateway
2464
2464
  esm_default.hardReset();
2465
2465
  }
2466
2466
  });
2467
+ test("verifyObject() rejects a key whose controller redirects to another actor", async () => {
2468
+ const keyId = new URL("https://evil.example/keys/ed25519");
2469
+ const victim = "https://victim.example/users/alice";
2470
+ const mallory = "https://evil.example/users/mallory";
2471
+ const multikeyDocument = await ed25519Multikey.toJsonLd({ contextLoader: mockDocumentLoader });
2472
+ const signOptions = {
2473
+ format: "compact",
2474
+ contextLoader: mockDocumentLoader,
2475
+ documentLoader: mockDocumentLoader,
2476
+ context: ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"]
2477
+ };
2478
+ assertEquals(await verifyObject(Create, await (await signObject(new Create({
2479
+ id: new URL("https://victim.example/activities/forged"),
2480
+ actor: new URL(victim),
2481
+ object: new Note({
2482
+ id: new URL("https://victim.example/notes/forged"),
2483
+ attribution: new URL(victim),
2484
+ content: "forged"
2485
+ })
2486
+ }), ed25519PrivateKey, keyId, signOptions)).toJsonLd(signOptions), {
2487
+ documentLoader(resource) {
2488
+ if (resource === keyId.href) return Promise.resolve({
2489
+ contextUrl: null,
2490
+ documentUrl: resource,
2491
+ document: {
2492
+ ...multikeyDocument,
2493
+ id: keyId.href,
2494
+ controller: victim
2495
+ }
2496
+ });
2497
+ if (resource === victim || resource === mallory) return Promise.resolve({
2498
+ contextUrl: null,
2499
+ documentUrl: mallory,
2500
+ document: {
2501
+ "@context": [
2502
+ "https://www.w3.org/ns/activitystreams",
2503
+ "https://w3id.org/security/v1",
2504
+ "https://w3id.org/security/multikey/v1",
2505
+ "https://w3id.org/security/data-integrity/v1",
2506
+ "https://www.w3.org/ns/did/v1"
2507
+ ],
2508
+ id: mallory,
2509
+ type: "Person",
2510
+ assertionMethod: [keyId.href]
2511
+ }
2512
+ });
2513
+ return mockDocumentLoader(resource);
2514
+ },
2515
+ contextLoader: mockDocumentLoader
2516
+ }), null);
2517
+ });
2467
2518
  //#endregion
2468
2519
  export {};
@@ -5,10 +5,10 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
6
  import "../std__assert-DvMDRPes.mjs";
7
7
  import { t as assert } from "../assert-D1Eafmd_.mjs";
8
- import { c as verifyPortableObjectProof } from "../proof-nIOXkRme.mjs";
9
- import { p as verifyRequestDetailed } from "../http-CQcqhcga.mjs";
10
- import { o as createFederation } from "../middleware-B582s7UO.mjs";
11
- import { t as doesActorOwnKey } from "../owner-CcyHGdSK.mjs";
8
+ import { c as verifyPortableObjectProof } from "../proof-bC_S5Pka.mjs";
9
+ import { p as verifyRequestDetailed } from "../http-wf5LuVhT.mjs";
10
+ import { o as createFederation } from "../middleware-VMeVcza7.mjs";
11
+ import { t as doesActorOwnKey } from "../owner-MYmQvfAW.mjs";
12
12
  import { Follow, Person } from "@fedify/vocab";
13
13
  import { mockDocumentLoader, test } from "@fedify/fixture";
14
14
  import { encodeHex } from "byte-encodings/hex";
@@ -4,9 +4,9 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
5
5
  import "../std__assert-DvMDRPes.mjs";
6
6
  import { t as assert } from "../assert-D1Eafmd_.mjs";
7
- import { c as verifyPortableObjectProof } from "../proof-nIOXkRme.mjs";
8
- import { p as verifyRequestDetailed } from "../http-CQcqhcga.mjs";
9
- import { t as doesActorOwnKey } from "../owner-CcyHGdSK.mjs";
7
+ import { c as verifyPortableObjectProof } from "../proof-bC_S5Pka.mjs";
8
+ import { p as verifyRequestDetailed } from "../http-wf5LuVhT.mjs";
9
+ import { t as doesActorOwnKey } from "../owner-MYmQvfAW.mjs";
10
10
  import { Create } from "@fedify/vocab";
11
11
  import { decodeMultibase, importDidKey } from "@fedify/vocab-runtime";
12
12
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -2,9 +2,9 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { a as verificationObservation } from "../verification-Dpfcw8ml.mjs";
5
- import { h as verifyJsonLd, p as signJsonLd } from "../ld-Qk4WqslS.mjs";
6
- import { a as signObject, s as verifyObject } from "../proof-nIOXkRme.mjs";
7
- import { l as signRequest, p as verifyRequestDetailed } from "../http-CQcqhcga.mjs";
5
+ import { h as verifyJsonLd, p as signJsonLd } from "../ld-DjK2r7Tq.mjs";
6
+ import { a as signObject, s as verifyObject } from "../proof-bC_S5Pka.mjs";
7
+ import { l as signRequest, p as verifyRequestDetailed } from "../http-wf5LuVhT.mjs";
8
8
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, o as rsaPublicKey1, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
9
9
  import { Activity, Create, CryptographicKey, Multikey } from "@fedify/vocab";
10
10
  import { FetchError } from "@fedify/vocab-runtime";
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { c as getNormalizationContextLoader } from "./ld-Qk4WqslS.mjs";
4
+ import { c as getNormalizationContextLoader } from "./ld-DjK2r7Tq.mjs";
5
5
  import jsonld from "@fedify/vocab-runtime/jsonld";
6
6
  //#region src/federation/temporal.ts
7
7
  function isPlainObject(value) {
@@ -6,7 +6,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as ManualClockKvStore } from "../kv-DBRWVx3Q.mjs";
7
7
  import { n as baseOptions, o as stringSchema, t as MockQueue } from "../tasks-IG3pHfvZ.mjs";
8
8
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
9
- import { o as createFederation } from "../middleware-B582s7UO.mjs";
9
+ import { o as createFederation } from "../middleware-VMeVcza7.mjs";
10
10
  //#region src/testing/kv.test.ts
11
11
  test("ManualClockKvStore keeps an entry at its exact expiration instant", async () => {
12
12
  const kv = new ManualClockKvStore();
@@ -5,9 +5,9 @@ import { t as esm_default } from "../esm-BHXXx4Wx.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
6
  import "../std__assert-DvMDRPes.mjs";
7
7
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
8
- import { f as verifyRequest } from "../http-CQcqhcga.mjs";
8
+ import { f as verifyRequest } from "../http-wf5LuVhT.mjs";
9
9
  import { i as rsaPrivateKey2 } from "../keys-DIJNHW-Y.mjs";
10
- import { t as getAuthenticatedDocumentLoader } from "../docloader-Wf9RroxL.mjs";
10
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-XwuoivPh.mjs";
11
11
  import { configure, reset } from "@logtape/logtape";
12
12
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
13
13
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -635,6 +635,70 @@ const unsupportedUrls = [
635
635
  "file:///collection",
636
636
  `data:application/ld+json,${encodeURIComponent(JSON.stringify({ id: "https://example.com/data" }))}`
637
637
  ];
638
+ function isCredentialUrlError(error) {
639
+ return error instanceof UrlError && error.reason === "disallowed" && error.message === "URLs with credentials are not allowed";
640
+ }
641
+ test("getAuthenticatedDocumentLoader() rejects URLs with credentials", async (t) => {
642
+ const identity = {
643
+ keyId: new URL("https://example.com/key2"),
644
+ privateKey: rsaPrivateKey2
645
+ };
646
+ const start = "https://8.8.8.8/start";
647
+ const target = "https://user:pass@8.8.8.8/document";
648
+ for (const allowPrivateAddress of [false, true]) {
649
+ const loader = getAuthenticatedDocumentLoader(identity, { allowPrivateAddress });
650
+ for (const path of [
651
+ "direct",
652
+ "redirect",
653
+ "Link",
654
+ "HTML"
655
+ ]) await t.step(`${path}, allowPrivateAddress=${allowPrivateAddress}`, async () => {
656
+ await withFetchStub((request) => {
657
+ if (request.url !== start) return Response.json({});
658
+ if (path === "redirect") return Response.redirect(target, 302);
659
+ if (path === "HTML") return new Response(`<link rel="alternate" type="application/activity+json" href="${target}">`, { headers: { "Content-Type": "text/html" } });
660
+ return new Response("", { headers: {
661
+ "Content-Type": "text/plain",
662
+ Link: `<${target}>; rel="alternate"; type="application/activity+json"`
663
+ } });
664
+ }, async (fetched) => {
665
+ await rejects(loader(path === "direct" ? target : start), isCredentialUrlError);
666
+ deepStrictEqual(fetched.map((r) => r.url), path === "direct" ? [] : [start]);
667
+ });
668
+ });
669
+ for (const url of [
670
+ "https://user@8.8.8.8/document",
671
+ "https://:pass@8.8.8.8/document",
672
+ "https://%75ser:p%40ss@8.8.8.8/document",
673
+ "http://user:pass@8.8.8.8/document"
674
+ ]) await t.step(`${url}, allowPrivateAddress=${allowPrivateAddress}`, async () => {
675
+ await withFetchStub(() => Response.json({}), async (fetched) => {
676
+ await rejects(loader(url), isCredentialUrlError);
677
+ deepStrictEqual(fetched, []);
678
+ });
679
+ });
680
+ for (const url of ["https://:@8.8.8.8/document", "https://8.8.8.8/@user?q=@pass"]) await t.step(`allows ${url}, allowPrivateAddress=${allowPrivateAddress}`, async () => {
681
+ await withFetchStub(() => Response.json({ done: true }), async (fetched) => {
682
+ deepStrictEqual((await loader(url)).document, { done: true });
683
+ deepStrictEqual(fetched.map((r) => r.url), [new URL(url).href]);
684
+ });
685
+ });
686
+ }
687
+ for (const firstSpec of ["rfc9421", "draft-cavage-http-signatures-12"]) await t.step(`redirect after fallback from ${firstSpec}`, async () => {
688
+ const loader = getAuthenticatedDocumentLoader(identity, {
689
+ allowPrivateAddress: true,
690
+ specDeterminer: {
691
+ determineSpec: () => firstSpec,
692
+ rememberSpec: () => {}
693
+ }
694
+ });
695
+ await withFetchStub((request) => request.url !== start ? Response.json({}) : request.headers.has("Signature-Input") === (firstSpec === "rfc9421") ? new Response(null, { status: 401 }) : Response.redirect(target, 302), async (fetched) => {
696
+ await rejects(loader(start), isCredentialUrlError);
697
+ deepStrictEqual(fetched.map((r) => r.url), [start, start]);
698
+ deepStrictEqual(fetched.map((r) => r.headers.has("Signature-Input")), [firstSpec === "rfc9421", firstSpec !== "rfc9421"]);
699
+ });
700
+ });
701
+ });
638
702
  function isUnsupportedProtocolError(error, url) {
639
703
  return error instanceof UrlError && error.message === `Unsupported protocol: ${new URL(url).protocol}` && error.reason === "disallowed";
640
704
  }