@fedify/fedify 2.0.34-dev.2422 → 2.0.34-dev.2423

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 (49) hide show
  1. package/dist/{builder-BtXTDEtv.mjs → builder-yrlWWSb2.mjs} +3 -3
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{deno-Bg4WjC9i.mjs → deno-VKs2mmuk.mjs} +1 -1
  4. package/dist/{docloader-CWSyV_hO.mjs → docloader-C_D_5OXh.mjs} +2 -2
  5. package/dist/federation/builder.test.mjs +1 -1
  6. package/dist/federation/handler.test.mjs +6 -6
  7. package/dist/federation/idempotency.test.mjs +2 -2
  8. package/dist/federation/inbox.test.mjs +1 -1
  9. package/dist/federation/keycache.test.mjs +18 -7
  10. package/dist/federation/middleware.test.mjs +6 -6
  11. package/dist/federation/mod.cjs +1 -1
  12. package/dist/federation/mod.js +1 -1
  13. package/dist/federation/send.test.mjs +3 -3
  14. package/dist/federation/temporal.test.mjs +1 -1
  15. package/dist/federation/webfinger.test.mjs +1 -1
  16. package/dist/{http-Dn8ezIc4.mjs → http-5zy1STVq.mjs} +2 -2
  17. package/dist/{http-Deg2-QT1.js → http-C_lGOZVR.js} +17 -3
  18. package/dist/{http-VhiSRRrv.cjs → http-CuYc-XkP.cjs} +17 -3
  19. package/dist/{inbox-rSxw1S-O.mjs → inbox-3nhkGSMW.mjs} +1 -1
  20. package/dist/{key-FMJ_Yb6V.mjs → key-wwpJQHM0.mjs} +17 -3
  21. package/dist/{keycache-D239uGT9.mjs → keycache-DBD6fzl1.mjs} +1 -1
  22. package/dist/{kv-cache-C7IqLuyP.js → kv-cache-DgG5Sj35.js} +1 -1
  23. package/dist/{kv-cache-DUShrOXf.cjs → kv-cache-DhMHcykY.cjs} +1 -1
  24. package/dist/{ld-DI6j9YHQ.mjs → ld-BCjStgTd.mjs} +2 -2
  25. package/dist/{middleware-DB7tyrwR.mjs → middleware-Bxav3na4.mjs} +1 -1
  26. package/dist/{middleware-C5MErD4a.cjs → middleware-CHSunb_b.cjs} +5 -5
  27. package/dist/{middleware-NjNJhU26.cjs → middleware-CSW_W0q6.cjs} +1 -1
  28. package/dist/{middleware-CngG9vaR.mjs → middleware-CeIMgD2N.mjs} +12 -12
  29. package/dist/{middleware-BGqkt25o.js → middleware-hfX624bD.js} +4 -4
  30. package/dist/mod.cjs +4 -4
  31. package/dist/mod.js +4 -4
  32. package/dist/nodeinfo/handler.test.mjs +1 -1
  33. package/dist/{owner-DBETJJJt.mjs → owner-adLePX_5.mjs} +2 -2
  34. package/dist/{proof-D-00d1it.js → proof-BAxqx8DQ.js} +1 -1
  35. package/dist/{proof-DjqYwruE.cjs → proof-CIg5BBtv.cjs} +1 -1
  36. package/dist/{proof-Dwr2jRLF.mjs → proof-De7RbPGn.mjs} +2 -2
  37. package/dist/{send-BJhGne0V.mjs → send-KK7FluNF.mjs} +2 -2
  38. package/dist/sig/http.test.mjs +2 -2
  39. package/dist/sig/key.test.mjs +197 -3
  40. package/dist/sig/ld.test.mjs +47 -2
  41. package/dist/sig/mod.cjs +2 -2
  42. package/dist/sig/mod.js +2 -2
  43. package/dist/sig/owner.test.mjs +46 -1
  44. package/dist/sig/proof.test.mjs +52 -1
  45. package/dist/{temporal-Dio7XHrt.mjs → temporal-D6Uf0KUy.mjs} +1 -1
  46. package/dist/utils/docloader.test.mjs +2 -2
  47. package/dist/utils/mod.cjs +1 -1
  48. package/dist/utils/mod.js +1 -1
  49. package/package.json +5 -5
package/dist/mod.js CHANGED
@@ -3,11 +3,11 @@ import { URLPattern } from "urlpattern-polyfill";
3
3
  import "./chunk-CRNNMoPX.js";
4
4
  import { n as autoIdAssigner, r as getDefaultActivityTransformers, t as actorDehydrator } from "./transformers-BGMIq1cs.js";
5
5
  import "./compat/mod.js";
6
- import { c as importJwk, i as exportJwk, n as signRequest, o as fetchKey, r as verifyRequest, s as generateCryptoKeyPair } from "./http-Deg2-QT1.js";
7
- import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "./middleware-BGqkt25o.js";
6
+ import { c as importJwk, i as exportJwk, n as signRequest, o as fetchKey, r as verifyRequest, s as generateCryptoKeyPair } from "./http-C_lGOZVR.js";
7
+ import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "./middleware-hfX624bD.js";
8
8
  import { n as getNodeInfo, r as parseNodeInfo, t as nodeInfoToJson } from "./types-miAYfiGO.js";
9
- import { _ as signJsonLd, a as doesActorOwnKey, b as verifySignature, d as createSignature, f as detachSignature, i as verifyProof, l as attachSignature, n as signObject, o as getKeyOwner, r as verifyObject, t as createProof, y as verifyJsonLd } from "./proof-D-00d1it.js";
10
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-C7IqLuyP.js";
9
+ import { _ as signJsonLd, a as doesActorOwnKey, b as verifySignature, d as createSignature, f as detachSignature, i as verifyProof, l as attachSignature, n as signObject, o as getKeyOwner, r as verifyObject, t as createProof, y as verifyJsonLd } from "./proof-BAxqx8DQ.js";
10
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-DgG5Sj35.js";
11
11
  import { InProcessMessageQueue, MemoryKvStore, ParallelMessageQueue } from "./federation/mod.js";
12
12
  import "./nodeinfo/mod.js";
13
13
  import "./runtime/mod.js";
@@ -5,7 +5,7 @@ import { n as createRequestContext } from "../context-Juj6bdHC.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
6
  import "../std__assert-DflMZha5.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
8
- import { a as createFederation, h as handleNodeInfoJrd, m as handleNodeInfo } from "../middleware-CngG9vaR.mjs";
8
+ import { a as createFederation, h as handleNodeInfoJrd, m as handleNodeInfo } from "../middleware-CeIMgD2N.mjs";
9
9
  import { test } from "@fedify/fixture";
10
10
  //#region src/nodeinfo/handler.test.ts
11
11
  test("handleNodeInfo()", async () => {
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Bg4WjC9i.mjs";
5
- import { n as fetchActorDocument, s as verifyKeyOwnership, t as exportJwk } from "./key-FMJ_Yb6V.mjs";
4
+ import { n as version, t as name } from "./deno-VKs2mmuk.mjs";
5
+ import { n as fetchActorDocument, s as verifyKeyOwnership, t as exportJwk } from "./key-wwpJQHM0.mjs";
6
6
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
7
7
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { getDocumentLoader } from "@fedify/vocab-runtime";
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { a as fetchActorDocument, d as name, f as version, i as exportJwk, l as validateCryptoKey, o as fetchKey, u as verifyKeyOwnership } from "./http-Deg2-QT1.js";
3
+ import { a as fetchActorDocument, d as name, f as version, i as exportJwk, l as validateCryptoKey, o as fetchKey, u as verifyKeyOwnership } from "./http-C_lGOZVR.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { Activity, CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1, getTypeId, isActor } from "@fedify/vocab";
6
6
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
- const require_http = require("./http-VhiSRRrv.cjs");
4
+ const require_http = require("./http-CuYc-XkP.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let _fedify_vocab = require("@fedify/vocab");
7
7
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -1,8 +1,8 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Bg4WjC9i.mjs";
5
- import { o as validateCryptoKey, r as fetchKey } from "./key-FMJ_Yb6V.mjs";
4
+ import { n as version, t as name } from "./deno-VKs2mmuk.mjs";
5
+ import { o as validateCryptoKey, r as fetchKey } from "./key-wwpJQHM0.mjs";
6
6
  import { Activity, DataIntegrityProof, Multikey, getTypeId } from "@fedify/vocab";
7
7
  import { SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { getLogger } from "@logtape/logtape";
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Bg4WjC9i.mjs";
5
- import { n as doubleKnock } from "./http-Dn8ezIc4.mjs";
4
+ import { n as version, t as name } from "./deno-VKs2mmuk.mjs";
5
+ import { n as doubleKnock } from "./http-5zy1STVq.mjs";
6
6
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
7
7
  import { FetchError, UrlError, validatePublicUrl } from "@fedify/vocab-runtime";
8
8
  import { getLogger } from "@logtape/logtape";
@@ -7,8 +7,8 @@ import { i as assertExists, t as assertStringIncludes } from "../std__assert-Dfl
7
7
  import { r as assertFalse, t as assertThrows } from "../assert_throws-D4DNWrux.mjs";
8
8
  import { t as assert } from "../assert-BhQd8QHP.mjs";
9
9
  import { t as assertRejects } from "../assert_rejects-WyYSTDDX.mjs";
10
- import { t as exportJwk } from "../key-FMJ_Yb6V.mjs";
11
- import { a as parseRfc9421Signature, c as timingSafeEqual, i as formatRfc9421SignatureParameters, l as verifyRequest, n as doubleKnock, o as parseRfc9421SignatureInput, r as formatRfc9421Signature, s as signRequest, t as createRfc9421SignatureBase } from "../http-Dn8ezIc4.mjs";
10
+ import { t as exportJwk } from "../key-wwpJQHM0.mjs";
11
+ import { a as parseRfc9421Signature, c as timingSafeEqual, i as formatRfc9421SignatureParameters, l as verifyRequest, n as doubleKnock, o as parseRfc9421SignatureInput, r as formatRfc9421Signature, s as signRequest, t as createRfc9421SignatureBase } from "../http-5zy1STVq.mjs";
12
12
  import { i as rsaPrivateKey2, l as rsaPublicKey5, o as rsaPublicKey1, s as rsaPublicKey2 } from "../keys-CCxRJhWa.mjs";
13
13
  import { mockDocumentLoader, test } from "@fedify/fixture";
14
14
  import { FetchError, exportSpki } from "@fedify/vocab-runtime";
@@ -3,12 +3,12 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
5
  import { n as assertStrictEquals } from "../std__assert-DflMZha5.mjs";
6
- import { t as assertThrows } from "../assert_throws-D4DNWrux.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
8
  import { t as assertRejects } from "../assert_rejects-WyYSTDDX.mjs";
9
- import { a as importJwk, i as generateCryptoKeyPair, o as validateCryptoKey, r as fetchKey, t as exportJwk } from "../key-FMJ_Yb6V.mjs";
9
+ import { a as importJwk, i as generateCryptoKeyPair, o as validateCryptoKey, r as fetchKey, t as exportJwk } from "../key-wwpJQHM0.mjs";
10
10
  import { c as rsaPublicKey3, i as rsaPrivateKey2, o as rsaPublicKey1, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-CCxRJhWa.mjs";
11
- import { g as wrapContextLoaderForJsonLd } from "../ld-DI6j9YHQ.mjs";
11
+ import { g as wrapContextLoaderForJsonLd } from "../ld-BCjStgTd.mjs";
12
12
  import { mockDocumentLoader, test } from "@fedify/fixture";
13
13
  import { CryptographicKey, Multikey, Person } from "@fedify/vocab";
14
14
  import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
@@ -655,5 +655,199 @@ test("fetchKey() records the controller an actor document establishes", async ()
655
655
  assertEquals(key?.controllerId, new URL(actorId));
656
656
  assert(key?.publicKey != null);
657
657
  });
658
+ test("fetchKey() rejects a key whose owner redirects to another actor", async () => {
659
+ const keyId = "https://evil.example/keys/1";
660
+ const victim = "https://victim.example/users/alice";
661
+ const mallory = "https://evil.example/users/mallory";
662
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
663
+ const cache = {};
664
+ assertEquals(await fetchKey(keyId, CryptographicKey, {
665
+ documentLoader(resource) {
666
+ if (resource === keyId) return Promise.resolve({
667
+ contextUrl: null,
668
+ documentUrl: resource,
669
+ document: {
670
+ "@context": "https://w3id.org/security/v1",
671
+ id: keyId,
672
+ type: "CryptographicKey",
673
+ owner: victim,
674
+ publicKeyPem
675
+ }
676
+ });
677
+ if (resource === victim || resource === mallory) return Promise.resolve({
678
+ contextUrl: null,
679
+ documentUrl: mallory,
680
+ document: {
681
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
682
+ id: mallory,
683
+ type: "Person",
684
+ publicKey: keyId
685
+ }
686
+ });
687
+ return mockDocumentLoader(resource);
688
+ },
689
+ contextLoader: mockDocumentLoader,
690
+ keyCache: {
691
+ get(keyId) {
692
+ return Promise.resolve(cache[keyId.href]);
693
+ },
694
+ set(keyId, key) {
695
+ cache[keyId.href] = key;
696
+ return Promise.resolve();
697
+ }
698
+ }
699
+ }), {
700
+ key: null,
701
+ cached: false
702
+ });
703
+ assertEquals(cache, { [keyId]: null });
704
+ });
705
+ test("fetchKey() rejects a key whose owner resolves to another actor on the same origin", async () => {
706
+ const keyId = "https://victim.example/keys/bob";
707
+ const victim = "https://victim.example/users/alice";
708
+ const bob = "https://victim.example/users/bob";
709
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
710
+ assertEquals(await fetchKey(keyId, CryptographicKey, {
711
+ documentLoader(resource) {
712
+ if (resource === keyId) return Promise.resolve({
713
+ contextUrl: null,
714
+ documentUrl: resource,
715
+ document: {
716
+ "@context": "https://w3id.org/security/v1",
717
+ id: keyId,
718
+ type: "CryptographicKey",
719
+ owner: victim,
720
+ publicKeyPem
721
+ }
722
+ });
723
+ if (resource === victim || resource === bob) return Promise.resolve({
724
+ contextUrl: null,
725
+ documentUrl: resource,
726
+ document: {
727
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
728
+ id: bob,
729
+ type: "Person",
730
+ publicKey: keyId
731
+ }
732
+ });
733
+ return mockDocumentLoader(resource);
734
+ },
735
+ contextLoader: mockDocumentLoader
736
+ }), {
737
+ key: null,
738
+ cached: false
739
+ });
740
+ });
741
+ test("fetchKey() rejects a Multikey whose controller redirects to another actor", async () => {
742
+ const keyId = "https://evil.example/keys/ed25519";
743
+ const victim = "https://victim.example/users/alice";
744
+ const mallory = "https://evil.example/users/mallory";
745
+ const multikeyDocument = await ed25519Multikey.toJsonLd({ contextLoader: mockDocumentLoader });
746
+ assertEquals(await fetchKey(keyId, Multikey, {
747
+ documentLoader(resource) {
748
+ if (resource === keyId) return Promise.resolve({
749
+ contextUrl: null,
750
+ documentUrl: resource,
751
+ document: {
752
+ ...multikeyDocument,
753
+ id: keyId,
754
+ controller: victim
755
+ }
756
+ });
757
+ if (resource === victim || resource === mallory) return Promise.resolve({
758
+ contextUrl: null,
759
+ documentUrl: mallory,
760
+ document: {
761
+ "@context": [
762
+ "https://www.w3.org/ns/activitystreams",
763
+ "https://w3id.org/security/v1",
764
+ "https://w3id.org/security/multikey/v1",
765
+ "https://w3id.org/security/data-integrity/v1",
766
+ "https://www.w3.org/ns/did/v1"
767
+ ],
768
+ id: mallory,
769
+ type: "Person",
770
+ assertionMethod: [keyId]
771
+ }
772
+ });
773
+ return mockDocumentLoader(resource);
774
+ },
775
+ contextLoader: mockDocumentLoader
776
+ }), {
777
+ key: null,
778
+ cached: false
779
+ });
780
+ });
781
+ test("fetchKey() accepts an owner whose document URL differs from its id", async () => {
782
+ const keyId = "https://victim.example/keys/alice";
783
+ const alice = "https://victim.example/users/alice";
784
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
785
+ const { key, cached } = await fetchKey(keyId, CryptographicKey, {
786
+ documentLoader(resource) {
787
+ if (resource === keyId) return Promise.resolve({
788
+ contextUrl: null,
789
+ documentUrl: resource,
790
+ document: {
791
+ "@context": "https://w3id.org/security/v1",
792
+ id: keyId,
793
+ type: "CryptographicKey",
794
+ owner: alice,
795
+ publicKeyPem
796
+ }
797
+ });
798
+ if (resource === alice) return Promise.resolve({
799
+ contextUrl: null,
800
+ documentUrl: `${alice}.json`,
801
+ document: {
802
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
803
+ id: alice,
804
+ type: "Person",
805
+ publicKey: keyId
806
+ }
807
+ });
808
+ return mockDocumentLoader(resource);
809
+ },
810
+ contextLoader: mockDocumentLoader
811
+ });
812
+ assertFalse(cached);
813
+ assertEquals(key?.id, new URL(keyId));
814
+ assertEquals(key?.ownerId, new URL(alice));
815
+ });
816
+ test("fetchKey() rejects an owner reached through a normalization redirect", async () => {
817
+ const keyId = "https://victim.example/keys/alice";
818
+ const canonical = "https://victim.example/users/alice";
819
+ const nonCanonical = "https://victim.example/users/Alice";
820
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
821
+ assertEquals(await fetchKey(keyId, CryptographicKey, {
822
+ documentLoader(resource) {
823
+ if (resource === keyId) return Promise.resolve({
824
+ contextUrl: null,
825
+ documentUrl: resource,
826
+ document: {
827
+ "@context": "https://w3id.org/security/v1",
828
+ id: keyId,
829
+ type: "CryptographicKey",
830
+ owner: nonCanonical,
831
+ publicKeyPem
832
+ }
833
+ });
834
+ if (resource === canonical || resource === nonCanonical) return Promise.resolve({
835
+ contextUrl: null,
836
+ documentUrl: canonical,
837
+ document: {
838
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
839
+ id: canonical,
840
+ type: "Person",
841
+ publicKey: keyId
842
+ }
843
+ });
844
+ return mockDocumentLoader(resource);
845
+ },
846
+ contextLoader: mockDocumentLoader
847
+ }), {
848
+ key: null,
849
+ cached: false
850
+ });
851
+ });
658
852
  //#endregion
659
853
  export {};
@@ -5,9 +5,9 @@ import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
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
7
  import { t as assertRejects } from "../assert_rejects-WyYSTDDX.mjs";
8
- import { i as generateCryptoKeyPair } from "../key-FMJ_Yb6V.mjs";
8
+ import { i as generateCryptoKeyPair } from "../key-wwpJQHM0.mjs";
9
9
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-CCxRJhWa.mjs";
10
- import { a as compactJsonLd, d as isInvalidUrlTypeError, f as signJsonLd, h as verifySignature, i as attachSignature, m as verifyJsonLd, n as UnsafeJsonLdError, o as createSignature, s as detachSignature } from "../ld-DI6j9YHQ.mjs";
10
+ import { a as compactJsonLd, d as isInvalidUrlTypeError, f as signJsonLd, h as verifySignature, i as attachSignature, m as verifyJsonLd, n as UnsafeJsonLdError, o as createSignature, s as detachSignature } from "../ld-BCjStgTd.mjs";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  import { CryptographicKey } from "@fedify/vocab";
13
13
  import { encodeBase64 } from "byte-encodings/base64";
@@ -818,5 +818,50 @@ test("verifyJsonLd() rejects a key that claims a forged owner", async () => {
818
818
  contextLoader: mockDocumentLoader
819
819
  }));
820
820
  });
821
+ test("verifyJsonLd() rejects a key whose owner redirects to another actor", async () => {
822
+ const keyId = new URL("https://evil.example/keys/1");
823
+ const victim = "https://victim.example/users/alice";
824
+ const mallory = "https://evil.example/users/mallory";
825
+ const { publicKeyPem } = await rsaPublicKey3.toJsonLd({ contextLoader: mockDocumentLoader });
826
+ const signed = await signJsonLd({
827
+ "@context": "https://www.w3.org/ns/activitystreams",
828
+ id: "https://victim.example/activities/forged",
829
+ type: "Create",
830
+ actor: victim,
831
+ object: {
832
+ type: "Note",
833
+ content: "forged"
834
+ }
835
+ }, rsaPrivateKey3, keyId, { contextLoader: mockDocumentLoader });
836
+ const options = {
837
+ documentLoader(resource) {
838
+ if (resource === keyId.href) return Promise.resolve({
839
+ contextUrl: null,
840
+ documentUrl: resource,
841
+ document: {
842
+ "@context": "https://w3id.org/security/v1",
843
+ id: keyId.href,
844
+ type: "CryptographicKey",
845
+ owner: victim,
846
+ publicKeyPem
847
+ }
848
+ });
849
+ if (resource === victim || resource === mallory) return Promise.resolve({
850
+ contextUrl: null,
851
+ documentUrl: mallory,
852
+ document: {
853
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
854
+ id: mallory,
855
+ type: "Person",
856
+ publicKey: keyId.href
857
+ }
858
+ });
859
+ return mockDocumentLoader(resource);
860
+ },
861
+ contextLoader: mockDocumentLoader
862
+ };
863
+ assertEquals(await verifySignature(signed, options), null);
864
+ assertFalse(await verifyJsonLd(signed, options));
865
+ });
821
866
  //#endregion
822
867
  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-VhiSRRrv.cjs");
5
- const require_proof = require("../proof-DjqYwruE.cjs");
4
+ const require_http = require("../http-CuYc-XkP.cjs");
5
+ const require_proof = require("../proof-CIg5BBtv.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 { c as importJwk, i as exportJwk, n as signRequest, o as fetchKey, r as verifyRequest, s as generateCryptoKeyPair } from "../http-Deg2-QT1.js";
4
- import { _ as signJsonLd, a as doesActorOwnKey, b as verifySignature, d as createSignature, f as detachSignature, i as verifyProof, l as attachSignature, n as signObject, o as getKeyOwner, r as verifyObject, t as createProof, y as verifyJsonLd } from "../proof-D-00d1it.js";
3
+ import { c as importJwk, i as exportJwk, n as signRequest, o as fetchKey, r as verifyRequest, s as generateCryptoKeyPair } from "../http-C_lGOZVR.js";
4
+ import { _ as signJsonLd, a as doesActorOwnKey, b as verifySignature, d as createSignature, f as detachSignature, i as verifyProof, l as attachSignature, n as signObject, o as getKeyOwner, r as verifyObject, t as createProof, y as verifyJsonLd } from "../proof-BAxqx8DQ.js";
5
5
  export { attachSignature, createProof, createSignature, detachSignature, doesActorOwnKey, exportJwk, fetchKey, generateCryptoKeyPair, getKeyOwner, importJwk, signJsonLd, signObject, signRequest, verifyJsonLd, verifyObject, verifyProof, verifyRequest, verifySignature };
@@ -6,7 +6,7 @@ import "../std__assert-DflMZha5.mjs";
6
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-CCxRJhWa.mjs";
9
- import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-DBETJJJt.mjs";
9
+ import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-adLePX_5.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 {};
@@ -6,7 +6,7 @@ import "../std__assert-DflMZha5.mjs";
6
6
  import { n as assertInstanceOf } from "../assert-BhQd8QHP.mjs";
7
7
  import { t as assertRejects } from "../assert_rejects-WyYSTDDX.mjs";
8
8
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-CCxRJhWa.mjs";
9
- import { i as verifyProof, n as signObject, r as verifyObject, t as createProof } from "../proof-Dwr2jRLF.mjs";
9
+ import { i as verifyProof, n as signObject, r as verifyObject, t as createProof } from "../proof-De7RbPGn.mjs";
10
10
  import { mockDocumentLoader, test } from "@fedify/fixture";
11
11
  import { Create, DataIntegrityProof, Multikey, Note, Place } from "@fedify/vocab";
12
12
  import { decodeMultibase, importMultibaseKey } from "@fedify/vocab-runtime";
@@ -322,5 +322,56 @@ test("verifyObject() rejects a key that claims a forged controller", async () =>
322
322
  contextLoader: mockDocumentLoader
323
323
  }), null);
324
324
  });
325
+ test("verifyObject() rejects a key whose controller redirects to another actor", async () => {
326
+ const keyId = new URL("https://evil.example/keys/ed25519");
327
+ const victim = "https://victim.example/users/alice";
328
+ const mallory = "https://evil.example/users/mallory";
329
+ const multikeyDocument = await ed25519Multikey.toJsonLd({ contextLoader: mockDocumentLoader });
330
+ const signOptions = {
331
+ format: "compact",
332
+ contextLoader: mockDocumentLoader,
333
+ documentLoader: mockDocumentLoader,
334
+ context: ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"]
335
+ };
336
+ assertEquals(await verifyObject(Create, await (await signObject(new Create({
337
+ id: new URL("https://victim.example/activities/forged"),
338
+ actor: new URL(victim),
339
+ object: new Note({
340
+ id: new URL("https://victim.example/notes/forged"),
341
+ attribution: new URL(victim),
342
+ content: "forged"
343
+ })
344
+ }), ed25519PrivateKey, keyId, signOptions)).toJsonLd(signOptions), {
345
+ documentLoader(resource) {
346
+ if (resource === keyId.href) return Promise.resolve({
347
+ contextUrl: null,
348
+ documentUrl: resource,
349
+ document: {
350
+ ...multikeyDocument,
351
+ id: keyId.href,
352
+ controller: victim
353
+ }
354
+ });
355
+ if (resource === victim || resource === mallory) return Promise.resolve({
356
+ contextUrl: null,
357
+ documentUrl: mallory,
358
+ document: {
359
+ "@context": [
360
+ "https://www.w3.org/ns/activitystreams",
361
+ "https://w3id.org/security/v1",
362
+ "https://w3id.org/security/multikey/v1",
363
+ "https://w3id.org/security/data-integrity/v1",
364
+ "https://www.w3.org/ns/did/v1"
365
+ ],
366
+ id: mallory,
367
+ type: "Person",
368
+ assertionMethod: [keyId.href]
369
+ }
370
+ });
371
+ return mockDocumentLoader(resource);
372
+ },
373
+ contextLoader: mockDocumentLoader
374
+ }), null);
375
+ });
325
376
  //#endregion
326
377
  export {};
@@ -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-DI6j9YHQ.mjs";
4
+ import { c as getNormalizationContextLoader } from "./ld-BCjStgTd.mjs";
5
5
  import jsonld from "@fedify/vocab-runtime/jsonld";
6
6
  //#region src/federation/temporal.ts
7
7
  function isPlainObject(value) {
@@ -5,9 +5,9 @@ import { t as esm_default } from "../esm-DVILvP5e.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
6
  import "../std__assert-DflMZha5.mjs";
7
7
  import { t as assertRejects } from "../assert_rejects-WyYSTDDX.mjs";
8
- import { l as verifyRequest } from "../http-Dn8ezIc4.mjs";
8
+ import { l as verifyRequest } from "../http-5zy1STVq.mjs";
9
9
  import { i as rsaPrivateKey2 } from "../keys-CCxRJhWa.mjs";
10
- import { t as getAuthenticatedDocumentLoader } from "../docloader-CWSyV_hO.mjs";
10
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-C_D_5OXh.mjs";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
13
13
  import { configure, reset } from "@logtape/logtape";
@@ -1,6 +1,6 @@
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_kv_cache = require("../kv-cache-DUShrOXf.cjs");
4
+ const require_kv_cache = require("../kv-cache-DhMHcykY.cjs");
5
5
  exports.getAuthenticatedDocumentLoader = require_kv_cache.getAuthenticatedDocumentLoader;
6
6
  exports.kvCache = require_kv_cache.kvCache;
package/dist/utils/mod.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-C7IqLuyP.js";
3
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-DgG5Sj35.js";
4
4
  export { getAuthenticatedDocumentLoader, kvCache };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/fedify",
3
- "version": "2.0.34-dev.2422+047ad834",
3
+ "version": "2.0.34-dev.2423+4999dc80",
4
4
  "description": "An ActivityPub server framework",
5
5
  "keywords": [
6
6
  "ActivityPub",
@@ -144,9 +144,9 @@
144
144
  "uri-template-router": "^1.0.0",
145
145
  "url-template": "^3.1.1",
146
146
  "urlpattern-polyfill": "^10.1.0",
147
- "@fedify/vocab-runtime": "2.0.34-dev.2422+047ad834",
148
- "@fedify/webfinger": "2.0.34-dev.2422+047ad834",
149
- "@fedify/vocab": "2.0.34-dev.2422+047ad834"
147
+ "@fedify/vocab-runtime": "2.0.34-dev.2423+4999dc80",
148
+ "@fedify/webfinger": "2.0.34-dev.2423+4999dc80",
149
+ "@fedify/vocab": "2.0.34-dev.2423+4999dc80"
150
150
  },
151
151
  "devDependencies": {
152
152
  "@std/assert": "npm:@jsr/std__assert@^0.226.0",
@@ -159,7 +159,7 @@
159
159
  "typescript": "^6.0.0",
160
160
  "wrangler": "^4.17.0",
161
161
  "@fedify/fixture": "2.0.0",
162
- "@fedify/vocab-tools": "^2.0.34-dev.2422+047ad834"
162
+ "@fedify/vocab-tools": "^2.0.34-dev.2423+4999dc80"
163
163
  },
164
164
  "scripts": {
165
165
  "build:self": "tsdown",