@fedify/fedify 2.2.18-dev.2386 → 2.2.19

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 (65) hide show
  1. package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-WyYSTDDX.mjs} +2 -20
  2. package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-D4DNWrux.mjs} +19 -1
  3. package/dist/{builder-CiKYvR0I.mjs → builder-UmydCxtK.mjs} +2 -2
  4. package/dist/compat/public-audience.test.mjs +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/{deno-CkabM5bf.mjs → deno-CD-8UlUi.mjs} +1 -1
  7. package/dist/{docloader-B4Hk3LSj.mjs → docloader-D1ghurDJ.mjs} +6 -2
  8. package/dist/federation/builder.test.mjs +3 -3
  9. package/dist/federation/collection.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +261 -8
  11. package/dist/federation/idempotency.test.mjs +3 -3
  12. package/dist/federation/inbox.test.mjs +25 -1
  13. package/dist/federation/keycache.test.mjs +18 -7
  14. package/dist/federation/kv.test.mjs +1 -1
  15. package/dist/federation/middleware.test.mjs +128 -10
  16. package/dist/federation/mod.cjs +1 -1
  17. package/dist/federation/mod.js +1 -1
  18. package/dist/federation/mq.test.mjs +2 -2
  19. package/dist/federation/negotiation.test.mjs +3 -3
  20. package/dist/federation/retry.test.mjs +2 -2
  21. package/dist/federation/router.test.mjs +2 -3
  22. package/dist/federation/send.test.mjs +7 -6
  23. package/dist/federation/temporal.test.mjs +3 -3
  24. package/dist/federation/webfinger.test.mjs +2 -2
  25. package/dist/{http-DovhI1VM.mjs → http-BBBUbwKh.mjs} +2 -2
  26. package/dist/{http-DP-vbG4o.js → http-DJK2udgX.js} +17 -3
  27. package/dist/{http-C_Qq0cNx.cjs → http-DrUG4fPS.cjs} +17 -3
  28. package/dist/inbox-Dca_0fwc.mjs +162 -0
  29. package/dist/{key-BtVvc9pe.mjs → key-DolXUaLH.mjs} +17 -3
  30. package/dist/{keycache-Dkc_sflV.mjs → keycache-BimEQucx.mjs} +1 -1
  31. package/dist/{kv-cache-Ds_adaor.cjs → kv-cache-Bk3V1Tiu.cjs} +5 -1
  32. package/dist/{kv-cache-D8yJIXRo.js → kv-cache-DDm80t1K.js} +5 -1
  33. package/dist/{ld-1J9cdTp0.mjs → ld-rTdIfZ63.mjs} +2 -2
  34. package/dist/{middleware-B_M6lcay.cjs → middleware-B4-Rzdi3.cjs} +1 -1
  35. package/dist/{middleware-BWXhlsuq.cjs → middleware-BWOwEGpy.cjs} +97 -27
  36. package/dist/{middleware-BrFMyQSp.js → middleware-BimXKBcP.js} +96 -26
  37. package/dist/{middleware-ruFPV2xR.mjs → middleware-CpXoRHs3.mjs} +85 -168
  38. package/dist/{middleware-DMkt5ShX.mjs → middleware-DYK_res8.mjs} +1 -1
  39. package/dist/mod.cjs +4 -4
  40. package/dist/mod.js +4 -4
  41. package/dist/nodeinfo/client.test.mjs +1 -1
  42. package/dist/nodeinfo/handler.test.mjs +2 -2
  43. package/dist/nodeinfo/types.test.mjs +2 -2
  44. package/dist/otel/exporter.test.mjs +1 -1
  45. package/dist/{owner-CgurOfJ9.mjs → owner-CVfzyM5V.mjs} +2 -2
  46. package/dist/{proof-BJ332wO_.js → proof-CaQ4KG6y.js} +1 -1
  47. package/dist/{proof-C6uAR-Hj.cjs → proof-DhaTTn-P.cjs} +1 -1
  48. package/dist/{proof-Dn4nm7pr.mjs → proof-gOGM-sJS.mjs} +2 -2
  49. package/dist/{send-iVwnK504.mjs → send-D35Va72B.mjs} +2 -2
  50. package/dist/sig/http.test.mjs +6 -6
  51. package/dist/sig/key.test.mjs +199 -5
  52. package/dist/sig/ld.test.mjs +49 -4
  53. package/dist/sig/mod.cjs +2 -2
  54. package/dist/sig/mod.js +2 -2
  55. package/dist/sig/owner.test.mjs +48 -3
  56. package/dist/sig/proof.test.mjs +55 -3
  57. package/dist/{std__assert-BVFj8-_V.mjs → std__assert-DNZ4Liil.mjs} +2 -2
  58. package/dist/{temporal-BZrzDMq2.mjs → temporal-CA4Y9hBJ.mjs} +1 -1
  59. package/dist/utils/docloader.test.mjs +178 -39
  60. package/dist/utils/mod.cjs +1 -1
  61. package/dist/utils/mod.js +1 -1
  62. package/package.json +5 -5
  63. /package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-BkxW5QJI.mjs} +0 -0
  64. /package/dist/{negotiation-BHtZN6Y1.mjs → negotiation-SQvQgUqe.mjs} +0 -0
  65. /package/dist/{retry-bMXBL97A.mjs → retry-v_sGLH1d.mjs} +0 -0
@@ -2,14 +2,14 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import "../std__assert-BVFj8-_V.mjs";
6
- import { t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
5
+ import "../std__assert-DNZ4Liil.mjs";
6
+ import { r as assertFalse, t as assertThrows } from "../assert_throws-D4DNWrux.mjs";
7
7
  import { t as assert } from "../assert-BhQd8QHP.mjs";
8
- import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
8
+ import { t as assertRejects } from "../assert_rejects-WyYSTDDX.mjs";
9
9
  import { t as assertStrictEquals } from "../assert_strict_equals-Dmjbg-bA.mjs";
10
- import { a as generateCryptoKeyPair, i as fetchKeyDetailed, o as importJwk, r as fetchKey, s as validateCryptoKey, t as exportJwk } from "../key-BtVvc9pe.mjs";
10
+ import { a as generateCryptoKeyPair, i as fetchKeyDetailed, o as importJwk, r as fetchKey, s as validateCryptoKey, t as exportJwk } from "../key-DolXUaLH.mjs";
11
11
  import { c as rsaPublicKey3, i as rsaPrivateKey2, o as rsaPublicKey1, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
12
- import { _ as wrapContextLoaderForJsonLd } from "../ld-1J9cdTp0.mjs";
12
+ import { _ as wrapContextLoaderForJsonLd } from "../ld-rTdIfZ63.mjs";
13
13
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
14
14
  import { CryptographicKey, Multikey, Person } from "@fedify/vocab";
15
15
  import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
@@ -760,5 +760,199 @@ test("fetchKey() records the controller an actor document establishes", async ()
760
760
  assertEquals(key?.controllerId, new URL(actorId));
761
761
  assert(key?.publicKey != null);
762
762
  });
763
+ test("fetchKey() rejects a key whose owner redirects to another actor", async () => {
764
+ const keyId = "https://evil.example/keys/1";
765
+ const victim = "https://victim.example/users/alice";
766
+ const mallory = "https://evil.example/users/mallory";
767
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
768
+ const cache = {};
769
+ assertEquals(await fetchKey(keyId, CryptographicKey, {
770
+ documentLoader(resource) {
771
+ if (resource === keyId) return Promise.resolve({
772
+ contextUrl: null,
773
+ documentUrl: resource,
774
+ document: {
775
+ "@context": "https://w3id.org/security/v1",
776
+ id: keyId,
777
+ type: "CryptographicKey",
778
+ owner: victim,
779
+ publicKeyPem
780
+ }
781
+ });
782
+ if (resource === victim || resource === mallory) return Promise.resolve({
783
+ contextUrl: null,
784
+ documentUrl: mallory,
785
+ document: {
786
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
787
+ id: mallory,
788
+ type: "Person",
789
+ publicKey: keyId
790
+ }
791
+ });
792
+ return mockDocumentLoader(resource);
793
+ },
794
+ contextLoader: mockDocumentLoader,
795
+ keyCache: {
796
+ get(keyId) {
797
+ return Promise.resolve(cache[keyId.href]);
798
+ },
799
+ set(keyId, key) {
800
+ cache[keyId.href] = key;
801
+ return Promise.resolve();
802
+ }
803
+ }
804
+ }), {
805
+ key: null,
806
+ cached: false
807
+ });
808
+ assertEquals(cache, { [keyId]: null });
809
+ });
810
+ test("fetchKey() rejects a key whose owner resolves to another actor on the same origin", async () => {
811
+ const keyId = "https://victim.example/keys/bob";
812
+ const victim = "https://victim.example/users/alice";
813
+ const bob = "https://victim.example/users/bob";
814
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
815
+ assertEquals(await fetchKey(keyId, CryptographicKey, {
816
+ documentLoader(resource) {
817
+ if (resource === keyId) return Promise.resolve({
818
+ contextUrl: null,
819
+ documentUrl: resource,
820
+ document: {
821
+ "@context": "https://w3id.org/security/v1",
822
+ id: keyId,
823
+ type: "CryptographicKey",
824
+ owner: victim,
825
+ publicKeyPem
826
+ }
827
+ });
828
+ if (resource === victim || resource === bob) return Promise.resolve({
829
+ contextUrl: null,
830
+ documentUrl: resource,
831
+ document: {
832
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
833
+ id: bob,
834
+ type: "Person",
835
+ publicKey: keyId
836
+ }
837
+ });
838
+ return mockDocumentLoader(resource);
839
+ },
840
+ contextLoader: mockDocumentLoader
841
+ }), {
842
+ key: null,
843
+ cached: false
844
+ });
845
+ });
846
+ test("fetchKey() rejects a Multikey whose controller redirects to another actor", async () => {
847
+ const keyId = "https://evil.example/keys/ed25519";
848
+ const victim = "https://victim.example/users/alice";
849
+ const mallory = "https://evil.example/users/mallory";
850
+ const multikeyDocument = await ed25519Multikey.toJsonLd({ contextLoader: mockDocumentLoader });
851
+ assertEquals(await fetchKey(keyId, Multikey, {
852
+ documentLoader(resource) {
853
+ if (resource === keyId) return Promise.resolve({
854
+ contextUrl: null,
855
+ documentUrl: resource,
856
+ document: {
857
+ ...multikeyDocument,
858
+ id: keyId,
859
+ controller: victim
860
+ }
861
+ });
862
+ if (resource === victim || resource === mallory) return Promise.resolve({
863
+ contextUrl: null,
864
+ documentUrl: mallory,
865
+ document: {
866
+ "@context": [
867
+ "https://www.w3.org/ns/activitystreams",
868
+ "https://w3id.org/security/v1",
869
+ "https://w3id.org/security/multikey/v1",
870
+ "https://w3id.org/security/data-integrity/v1",
871
+ "https://www.w3.org/ns/did/v1"
872
+ ],
873
+ id: mallory,
874
+ type: "Person",
875
+ assertionMethod: [keyId]
876
+ }
877
+ });
878
+ return mockDocumentLoader(resource);
879
+ },
880
+ contextLoader: mockDocumentLoader
881
+ }), {
882
+ key: null,
883
+ cached: false
884
+ });
885
+ });
886
+ test("fetchKey() accepts an owner whose document URL differs from its id", async () => {
887
+ const keyId = "https://victim.example/keys/alice";
888
+ const alice = "https://victim.example/users/alice";
889
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
890
+ const { key, cached } = await fetchKey(keyId, CryptographicKey, {
891
+ documentLoader(resource) {
892
+ if (resource === keyId) return Promise.resolve({
893
+ contextUrl: null,
894
+ documentUrl: resource,
895
+ document: {
896
+ "@context": "https://w3id.org/security/v1",
897
+ id: keyId,
898
+ type: "CryptographicKey",
899
+ owner: alice,
900
+ publicKeyPem
901
+ }
902
+ });
903
+ if (resource === alice) return Promise.resolve({
904
+ contextUrl: null,
905
+ documentUrl: `${alice}.json`,
906
+ document: {
907
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
908
+ id: alice,
909
+ type: "Person",
910
+ publicKey: keyId
911
+ }
912
+ });
913
+ return mockDocumentLoader(resource);
914
+ },
915
+ contextLoader: mockDocumentLoader
916
+ });
917
+ assertFalse(cached);
918
+ assertEquals(key?.id, new URL(keyId));
919
+ assertEquals(key?.ownerId, new URL(alice));
920
+ });
921
+ test("fetchKey() rejects an owner reached through a normalization redirect", async () => {
922
+ const keyId = "https://victim.example/keys/alice";
923
+ const canonical = "https://victim.example/users/alice";
924
+ const nonCanonical = "https://victim.example/users/Alice";
925
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
926
+ assertEquals(await fetchKey(keyId, CryptographicKey, {
927
+ documentLoader(resource) {
928
+ if (resource === keyId) return Promise.resolve({
929
+ contextUrl: null,
930
+ documentUrl: resource,
931
+ document: {
932
+ "@context": "https://w3id.org/security/v1",
933
+ id: keyId,
934
+ type: "CryptographicKey",
935
+ owner: nonCanonical,
936
+ publicKeyPem
937
+ }
938
+ });
939
+ if (resource === canonical || resource === nonCanonical) return Promise.resolve({
940
+ contextUrl: null,
941
+ documentUrl: canonical,
942
+ document: {
943
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
944
+ id: canonical,
945
+ type: "Person",
946
+ publicKey: keyId
947
+ }
948
+ });
949
+ return mockDocumentLoader(resource);
950
+ },
951
+ contextLoader: mockDocumentLoader
952
+ }), {
953
+ key: null,
954
+ cached: false
955
+ });
956
+ });
763
957
  //#endregion
764
958
  export {};
@@ -2,12 +2,12 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import { n as assertFalse, t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
5
+ import { r as assertFalse, t as assertThrows } from "../assert_throws-D4DNWrux.mjs";
6
6
  import { n as assertInstanceOf, t as assert } from "../assert-BhQd8QHP.mjs";
7
- import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
8
- import { a as generateCryptoKeyPair } from "../key-BtVvc9pe.mjs";
7
+ import { t as assertRejects } from "../assert_rejects-WyYSTDDX.mjs";
8
+ import { a as generateCryptoKeyPair } from "../key-DolXUaLH.mjs";
9
9
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
10
- import { 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-1J9cdTp0.mjs";
10
+ import { 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-rTdIfZ63.mjs";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  import { CryptographicKey } from "@fedify/vocab";
13
13
  import { encodeBase64 } from "byte-encodings/base64";
@@ -860,5 +860,50 @@ test("verifyJsonLd() rejects a key that claims a forged owner", async () => {
860
860
  contextLoader: mockDocumentLoader
861
861
  }));
862
862
  });
863
+ test("verifyJsonLd() rejects a key whose owner redirects to another actor", async () => {
864
+ const keyId = new URL("https://evil.example/keys/1");
865
+ const victim = "https://victim.example/users/alice";
866
+ const mallory = "https://evil.example/users/mallory";
867
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
868
+ const signed = await signJsonLd({
869
+ "@context": "https://www.w3.org/ns/activitystreams",
870
+ id: "https://victim.example/activities/forged",
871
+ type: "Create",
872
+ actor: victim,
873
+ object: {
874
+ type: "Note",
875
+ content: "forged"
876
+ }
877
+ }, rsaPrivateKey3, keyId, { contextLoader: mockDocumentLoader });
878
+ const options = {
879
+ documentLoader(resource) {
880
+ if (resource === keyId.href) return Promise.resolve({
881
+ contextUrl: null,
882
+ documentUrl: resource,
883
+ document: {
884
+ "@context": "https://w3id.org/security/v1",
885
+ id: keyId.href,
886
+ type: "CryptographicKey",
887
+ owner: victim,
888
+ publicKeyPem
889
+ }
890
+ });
891
+ if (resource === victim || resource === mallory) return Promise.resolve({
892
+ contextUrl: null,
893
+ documentUrl: mallory,
894
+ document: {
895
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
896
+ id: mallory,
897
+ type: "Person",
898
+ publicKey: keyId.href
899
+ }
900
+ });
901
+ return mockDocumentLoader(resource);
902
+ },
903
+ contextLoader: mockDocumentLoader
904
+ };
905
+ assertEquals(await verifySignature(signed, options), null);
906
+ assertFalse(await verifyJsonLd(signed, options));
907
+ });
863
908
  //#endregion
864
909
  export {};
package/dist/sig/mod.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
- const require_http = require("../http-C_Qq0cNx.cjs");
5
- const require_proof = require("../proof-C6uAR-Hj.cjs");
4
+ const require_http = require("../http-DrUG4fPS.cjs");
5
+ const require_proof = require("../proof-DhaTTn-P.cjs");
6
6
  exports.attachSignature = require_proof.attachSignature;
7
7
  exports.createProof = require_proof.createProof;
8
8
  exports.createSignature = require_proof.createSignature;
package/dist/sig/mod.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { _ as validateAcceptSignature, a as verifyRequestDetailed, c as fetchKey, d as importJwk, g as parseAcceptSignature, h as fulfillAcceptSignature, i as verifyRequest, l as fetchKeyDetailed, m as formatAcceptSignature, o as exportJwk, r as signRequest, u as generateCryptoKeyPair } from "../http-DP-vbG4o.js";
4
- import { C as verifySignature, S as verifyJsonLd, _ as hasSignatureLike, a as verifyProof, b as signJsonLd, c as getKeyOwner, d as attachSignature, i as verifyObject, m as detachSignature, n as hasProofLike, p as createSignature, r as signObject, s as doesActorOwnKey, t as createProof } from "../proof-BJ332wO_.js";
3
+ import { _ as validateAcceptSignature, a as verifyRequestDetailed, c as fetchKey, d as importJwk, g as parseAcceptSignature, h as fulfillAcceptSignature, i as verifyRequest, l as fetchKeyDetailed, m as formatAcceptSignature, o as exportJwk, r as signRequest, u as generateCryptoKeyPair } from "../http-DJK2udgX.js";
4
+ import { C as verifySignature, S as verifyJsonLd, _ as hasSignatureLike, a as verifyProof, b as signJsonLd, c as getKeyOwner, d as attachSignature, i as verifyObject, m as detachSignature, n as hasProofLike, p as createSignature, r as signObject, s as doesActorOwnKey, t as createProof } from "../proof-CaQ4KG6y.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, verifyProof, verifyRequest, verifyRequestDetailed, verifySignature };
@@ -2,11 +2,11 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import "../std__assert-BVFj8-_V.mjs";
6
- import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
5
+ import "../std__assert-DNZ4Liil.mjs";
6
+ import { r as assertFalse } from "../assert_throws-D4DNWrux.mjs";
7
7
  import { t as assert } from "../assert-BhQd8QHP.mjs";
8
8
  import { o as rsaPublicKey1, s as rsaPublicKey2 } from "../keys-BAK-tUlf.mjs";
9
- import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-CgurOfJ9.mjs";
9
+ import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-CVfzyM5V.mjs";
10
10
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
11
11
  import { Create, CryptographicKey, lookupObject } from "@fedify/vocab";
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,12 +2,13 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import "../std__assert-BVFj8-_V.mjs";
6
- import { n as assertFalse, t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
5
+ import "../std__assert-DNZ4Liil.mjs";
6
+ import { r as assertFalse } from "../assert_throws-D4DNWrux.mjs";
7
7
  import { n as assertInstanceOf, t as assert } from "../assert-BhQd8QHP.mjs";
8
+ import { t as assertRejects } from "../assert_rejects-WyYSTDDX.mjs";
8
9
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
9
10
  import { r as normalizeOutgoingActivityJsonLd } from "../outgoing-jsonld-amccRMrV.mjs";
10
- import { a as verifyProof, i as verifyObject, n as hasProofLike, r as signObject, t as createProof } from "../proof-Dn4nm7pr.mjs";
11
+ import { a as verifyProof, i as verifyObject, n as hasProofLike, r as signObject, t as createProof } from "../proof-gOGM-sJS.mjs";
11
12
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
13
  import { Create, DataIntegrityProof, Document, Multikey, Note, PUBLIC_COLLECTION, Place } from "@fedify/vocab";
13
14
  import { decodeMultibase, importMultibaseKey } from "@fedify/vocab-runtime";
@@ -437,5 +438,56 @@ test("verifyObject() rejects a key that claims a forged controller", async () =>
437
438
  contextLoader: mockDocumentLoader
438
439
  }), null);
439
440
  });
441
+ test("verifyObject() rejects a key whose controller redirects to another actor", async () => {
442
+ const keyId = new URL("https://evil.example/keys/ed25519");
443
+ const victim = "https://victim.example/users/alice";
444
+ const mallory = "https://evil.example/users/mallory";
445
+ const multikeyDocument = await ed25519Multikey.toJsonLd({ contextLoader: mockDocumentLoader });
446
+ const signOptions = {
447
+ format: "compact",
448
+ contextLoader: mockDocumentLoader,
449
+ documentLoader: mockDocumentLoader,
450
+ context: ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"]
451
+ };
452
+ assertEquals(await verifyObject(Create, await (await signObject(new Create({
453
+ id: new URL("https://victim.example/activities/forged"),
454
+ actor: new URL(victim),
455
+ object: new Note({
456
+ id: new URL("https://victim.example/notes/forged"),
457
+ attribution: new URL(victim),
458
+ content: "forged"
459
+ })
460
+ }), ed25519PrivateKey, keyId, signOptions)).toJsonLd(signOptions), {
461
+ documentLoader(resource) {
462
+ if (resource === keyId.href) return Promise.resolve({
463
+ contextUrl: null,
464
+ documentUrl: resource,
465
+ document: {
466
+ ...multikeyDocument,
467
+ id: keyId.href,
468
+ controller: victim
469
+ }
470
+ });
471
+ if (resource === victim || resource === mallory) return Promise.resolve({
472
+ contextUrl: null,
473
+ documentUrl: mallory,
474
+ document: {
475
+ "@context": [
476
+ "https://www.w3.org/ns/activitystreams",
477
+ "https://w3id.org/security/v1",
478
+ "https://w3id.org/security/multikey/v1",
479
+ "https://w3id.org/security/data-integrity/v1",
480
+ "https://www.w3.org/ns/did/v1"
481
+ ],
482
+ id: mallory,
483
+ type: "Person",
484
+ assertionMethod: [keyId.href]
485
+ }
486
+ });
487
+ return mockDocumentLoader(resource);
488
+ },
489
+ contextLoader: mockDocumentLoader
490
+ }), null);
491
+ });
440
492
  //#endregion
441
493
  export {};
@@ -2,8 +2,8 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { l as AssertionError, s as format } from "./assert_equals-Ew3jOFa3.mjs";
5
- import "./assert_rejects-B-qJtC9Z.mjs";
6
- import "./assert_throws-4NwKEy2q.mjs";
5
+ import "./assert_throws-D4DNWrux.mjs";
6
+ import "./assert_rejects-WyYSTDDX.mjs";
7
7
  import "./assert_strict_equals-Dmjbg-bA.mjs";
8
8
  //#region ../../node_modules/.pnpm/@jsr+std__assert@0.226.0/node_modules/@jsr/std__assert/assert_exists.js
9
9
  /**
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { c as getNormalizationContextLoader } from "./ld-1J9cdTp0.mjs";
4
+ import { c as getNormalizationContextLoader } from "./ld-rTdIfZ63.mjs";
5
5
  import jsonld from "@fedify/vocab-runtime/jsonld";
6
6
  //#region src/federation/temporal.ts
7
7
  function isPlainObject(value) {