@fedify/fedify 2.4.0-dev.2084 → 2.4.0-dev.2093

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 (75) hide show
  1. package/dist/{builder-BGlpUocK.mjs → builder-Ld4Y9ymR.mjs} +34 -3
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{compound-proof-B6Aj2kGY.mjs → compound-proof-CTx_lewQ.mjs} +191 -13
  4. package/dist/{deno-Dn-py9Zy.mjs → deno-8t8k_6Yv.mjs} +1 -1
  5. package/dist/{docloader-BUq5bYJr.mjs → docloader-DJKcsAiF.mjs} +2 -2
  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 +5 -5
  10. package/dist/federation/hashlink-media.test.mjs +2 -2
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/metrics.test.mjs +1 -1
  13. package/dist/federation/middleware.test.mjs +7 -7
  14. package/dist/federation/mod.cjs +1 -1
  15. package/dist/federation/mod.js +1 -1
  16. package/dist/federation/outgoing-proof.test.mjs +254 -5
  17. package/dist/federation/portable-gateway-keys.test.mjs +72 -11
  18. package/dist/federation/portable-inbox.test.mjs +116 -4
  19. package/dist/federation/portable.test.mjs +182 -4
  20. package/dist/federation/send.test.mjs +3 -3
  21. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  22. package/dist/federation/tasks/tasks.test.mjs +2 -2
  23. package/dist/federation/temporal.test.mjs +1 -1
  24. package/dist/federation/webfinger.test.mjs +89 -1
  25. package/dist/{federation-BLGx8JUH.cjs → federation-aSHO04ob.cjs} +360 -65
  26. package/dist/{http-B0VRNipE.mjs → http-B2ftY0Ha.mjs} +4 -4
  27. package/dist/{http-B5q1CHtL.cjs → http-DSaSAN7X.cjs} +1609 -1457
  28. package/dist/{http-4PCCvhsU.js → http-DyT7DP1Y.js} +1518 -1390
  29. package/dist/{key-BJrp524f.mjs → key-Dwm4VswT.mjs} +39 -99
  30. package/dist/{kv-cache-Dhhhrryv.cjs → kv-cache-BSJAlP4C.cjs} +1 -1
  31. package/dist/{kv-cache-C4C3LyYW.mjs → kv-cache-D0yYukqk.mjs} +1 -1
  32. package/dist/{kv-cache-1KUmAPH7.js → kv-cache-DmroAz3M.js} +1 -1
  33. package/dist/{ld-BmFz5NLA.mjs → ld-BYZOoDzZ.mjs} +3 -3
  34. package/dist/{metrics-Beod6tzO.mjs → metrics-B7YwKTFr.mjs} +1 -1
  35. package/dist/{middleware-B7CNsyqt.mjs → middleware-BNeqKSsL.mjs} +1 -1
  36. package/dist/{middleware-D_9pNYe9.js → middleware-Blq0882d.js} +368 -73
  37. package/dist/{middleware-BKeQYPm0.mjs → middleware-E_0IH72m.mjs} +166 -71
  38. package/dist/{mod-HDSvS-ww.d.cts → mod-C8LieBzF.d.cts} +16 -3
  39. package/dist/{mod-C9WSpZWm.d.ts → mod-Coacostr.d.ts} +16 -3
  40. package/dist/mod.cjs +5 -5
  41. package/dist/mod.d.cts +1 -1
  42. package/dist/mod.d.ts +1 -1
  43. package/dist/mod.js +5 -5
  44. package/dist/nodeinfo/handler.test.mjs +1 -1
  45. package/dist/{owner-CsKVgMzk.mjs → owner-Cgx2Dc7I.mjs} +5 -4
  46. package/dist/{owner-_pbbgLJK.js → owner-DAGSC5gX.js} +2 -2
  47. package/dist/{owner-BQFZtFV4.cjs → owner-szIZl4qI.cjs} +2 -2
  48. package/dist/{portable-key-B4R-CiIV.mjs → portable-key-Cn1WJSN1.mjs} +60 -41
  49. package/dist/portable-key-id-Bva5iT3W.mjs +136 -0
  50. package/dist/{proof-ozG4HfcQ.mjs → proof-DVXC6nYz.mjs} +59 -18
  51. package/dist/{send-ClPt8nYu.mjs → send-D-hOvYKy.mjs} +3 -3
  52. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  53. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  54. package/dist/sig/compound-proof-verification.test.mjs +160 -3
  55. package/dist/sig/compound-proof.test.mjs +2 -2
  56. package/dist/sig/http.test.mjs +2 -2
  57. package/dist/sig/key.test.mjs +1 -1
  58. package/dist/sig/ld.test.mjs +2 -2
  59. package/dist/sig/mod.cjs +3 -3
  60. package/dist/sig/mod.d.cts +1 -1
  61. package/dist/sig/mod.d.ts +1 -1
  62. package/dist/sig/mod.js +3 -3
  63. package/dist/sig/owner.test.mjs +1 -1
  64. package/dist/sig/portable-collection.test.mjs +69 -5
  65. package/dist/sig/portable-key.test.mjs +176 -10
  66. package/dist/sig/proof.test.mjs +107 -4
  67. package/dist/{sig-DhdomAN0.js → sig-B5Ell7pO.js} +19 -3
  68. package/dist/{sig-k3C8sF7g.cjs → sig-CXOKN4Br.cjs} +19 -3
  69. package/dist/{temporal-Ba0EAZAI.mjs → temporal-CqpKGEPk.mjs} +1 -1
  70. package/dist/testing/kv.test.mjs +1 -1
  71. package/dist/utils/docloader.test.mjs +2 -2
  72. package/dist/utils/kv-cache.test.mjs +1 -1
  73. package/dist/utils/mod.cjs +1 -1
  74. package/dist/utils/mod.js +1 -1
  75. package/package.json +7 -7
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { B as getCanonicalPortableId, G as isSamePublicKey$1, M as exportJwk, N as fetchActorDocument, U as isCompatibleKeyId, ht as version, mt as name, o as fetchPortableGatewayKey, z as verifyKeyOwnership } from "./http-4PCCvhsU.js";
3
+ import { F as fetchActorDocument, P as exportJwk, V as verifyKeyOwnership, _t as isSamePublicKey$1, ht as isPortableId, mt as isCompatibleKeyId, o as fetchPortableGatewayKey, ut as getCanonicalPortableId, vt as name, yt as version } from "./http-DyT7DP1Y.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
6
6
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -173,7 +173,7 @@ async function getKeyOwner(keyId, options) {
173
173
  return await verifyKeyOwnership(object.id == null ? object.clone({ id: keyId }) : object, fetchOptions);
174
174
  }
175
175
  if (!isActor(object)) return null;
176
- if (object.id == null || object.id.origin !== documentUrl.origin) {
176
+ if (object.id == null || isPortableId(object.id) || object.id.origin !== documentUrl.origin) {
177
177
  logger.debug("The document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin; refusing to treat it as the owner of key {keyId}.", {
178
178
  documentUrl: documentUrl.href,
179
179
  actorId: object.id?.href,
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http-B5q1CHtL.cjs");
2
+ const require_http = require("./http-DSaSAN7X.cjs");
3
3
  let _logtape_logtape = require("@logtape/logtape");
4
4
  let _fedify_vocab = require("@fedify/vocab");
5
5
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -172,7 +172,7 @@ async function getKeyOwner(keyId, options) {
172
172
  return await require_http.verifyKeyOwnership(object.id == null ? object.clone({ id: keyId }) : object, fetchOptions);
173
173
  }
174
174
  if (!(0, _fedify_vocab.isActor)(object)) return null;
175
- if (object.id == null || object.id.origin !== documentUrl.origin) {
175
+ if (object.id == null || require_http.isPortableId(object.id) || object.id.origin !== documentUrl.origin) {
176
176
  logger.debug("The document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin; refusing to treat it as the owner of key {keyId}.", {
177
177
  documentUrl: documentUrl.href,
178
178
  actorId: object.id?.href,
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { h as isSamePublicKey, l as getCanonicalPortableId, p as isPortableActorDocument } from "./key-BJrp524f.mjs";
5
- import { c as verifyPortableObjectProofWithRoot } from "./proof-ozG4HfcQ.mjs";
4
+ import { l as isSamePublicKey, o as isPortableActorDocument, t as getCanonicalPortableId } from "./portable-key-id-Bva5iT3W.mjs";
5
+ import { l as verifyPortableObjectProofWithRoot } from "./proof-DVXC6nYz.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { CryptographicKey, Multikey, Object as Object$1 } from "@fedify/vocab";
8
8
  import { getDocumentLoader, isGatewayUrl } from "@fedify/vocab-runtime";
@@ -19,13 +19,19 @@ const SEC = "https://w3id.org/security#";
19
19
  *
20
20
  * A gateway key is accepted only if:
21
21
  *
22
- * - the document is an actor whose portable ID is the one the key ID is
23
- * the compatible identifier of;
22
+ * - the document is an actor whose portable ID, i.e., its `ap:` ID or
23
+ * the canonical portable ID of its compatible identifier, is the one
24
+ * the key ID is the compatible identifier of;
24
25
  * - the document has a valid [FEP-8b32] Object Integrity Proof made by
25
26
  * the DID of its ID, i.e., it satisfies the [FEP-ef61] proof policy;
26
27
  * - the document embeds a `Multikey` with that ID in its `assertionMethod`,
27
- * whose `controller` is the actor, as [FEP-521a] requires; a key
28
- * embedded in its `publicKey` with the same ID must agree with it; and
28
+ * whose `controller` is the actor, as [FEP-521a] requires, and a key
29
+ * embedded in its `publicKey` with the same ID agrees with it; or, if
30
+ * no `assertionMethod` entry has that ID, the document embeds
31
+ * a `CryptographicKey` with that ID in its `publicKey`, whose `owner` is
32
+ * the actor, as some publishers, e.g., tootik, list their RSA keys only
33
+ * there;
34
+ * - no more than one entry of either property has that ID; and
29
35
  * - the key ID's origin is one of the actor's `gateways`.
30
36
  *
31
37
  * [FEP-8b32]: https://w3id.org/fep/8b32
@@ -74,6 +80,21 @@ async function verifyPortableGatewayKeyDocument(document, actor, keyId, options
74
80
  if (!result.verified) return reject("The actor document does not satisfy the FEP-ef61 proof policy: " + result.reason.type);
75
81
  if (root == null) return reject("The actor document could not be expanded.");
76
82
  const refuse = (url) => Promise.reject(/* @__PURE__ */ new Error(`Refusing to fetch ${url}.`));
83
+ const parseEmbeddedPublicKey = async (node) => {
84
+ if (isReference(node)) return null;
85
+ let key;
86
+ try {
87
+ key = await CryptographicKey.fromJsonLd(node, {
88
+ documentLoader: refuse,
89
+ contextLoader: options.contextLoader,
90
+ tracerProvider: options.tracerProvider
91
+ });
92
+ } catch (error) {
93
+ if (!(error instanceof TypeError)) throw error;
94
+ return null;
95
+ }
96
+ return key.publicKey == null ? null : key;
97
+ };
77
98
  let verifiedActor;
78
99
  try {
79
100
  verifiedActor = await Object$1.fromJsonLd(root, {
@@ -87,41 +108,42 @@ async function verifyPortableGatewayKeyDocument(document, actor, keyId, options
87
108
  }
88
109
  if (!isPortableActorDocument(verifiedActor) || getCanonicalPortableId(verifiedActor.id) !== claimedActorId) return reject("The key ID is not a compatible identifier of the actor whose document is signed.");
89
110
  actor = verifiedActor;
90
- const multikeyNode = findEmbeddedNode(root[`${SEC}assertionMethod`], keyId);
91
- if (multikeyNode == null) return reject("The actor document does not embed the key in its assertionMethod.");
92
- let multikey;
93
- try {
94
- multikey = await Multikey.fromJsonLd(multikeyNode, {
95
- documentLoader: refuse,
96
- contextLoader: options.contextLoader,
97
- tracerProvider: options.tracerProvider
98
- });
99
- } catch (error) {
100
- if (!(error instanceof TypeError)) throw error;
101
- return reject("The key in the assertionMethod is malformed.");
102
- }
103
- if (multikey.publicKey == null) return reject("The key in the assertionMethod has no public key.");
104
- if (multikey.controllerId == null || getCanonicalPortableId(multikey.controllerId) !== actorId) return reject("The controller of the key is not the actor.");
105
- const publicKeyNode = findEmbeddedNode(root[`${SEC}publicKey`], keyId);
106
- if (publicKeyNode != null) {
107
- let publicKey;
111
+ const assertionNodes = findNodes(root[`${SEC}assertionMethod`], keyId);
112
+ const publicKeyNodes = findNodes(root[`${SEC}publicKey`], keyId);
113
+ if (assertionNodes.length > 1 || publicKeyNodes.length > 1) return reject("The actor document has more than one key with the ID.");
114
+ let publicKey;
115
+ if (assertionNodes.length > 0) {
116
+ if (isReference(assertionNodes[0])) return reject("The actor document does not embed the key in its assertionMethod.");
117
+ let multikey;
108
118
  try {
109
- publicKey = await CryptographicKey.fromJsonLd(publicKeyNode, {
119
+ multikey = await Multikey.fromJsonLd(assertionNodes[0], {
110
120
  documentLoader: refuse,
111
121
  contextLoader: options.contextLoader,
112
122
  tracerProvider: options.tracerProvider
113
123
  });
114
124
  } catch (error) {
115
125
  if (!(error instanceof TypeError)) throw error;
116
- return reject("The key in the publicKey is malformed.");
126
+ return reject("The key in the assertionMethod is malformed.");
127
+ }
128
+ if (multikey.publicKey == null) return reject("The key in the assertionMethod has no public key.");
129
+ if (multikey.controllerId == null || getCanonicalPortableId(multikey.controllerId) !== actorId) return reject("The controller of the key is not the actor.");
130
+ publicKey = multikey.publicKey;
131
+ if (publicKeyNodes.length > 0 && !isReference(publicKeyNodes[0])) {
132
+ const embedded = await parseEmbeddedPublicKey(publicKeyNodes[0]);
133
+ if (embedded == null || !await isSamePublicKey(embedded.publicKey, publicKey) || embedded.ownerId != null && getCanonicalPortableId(embedded.ownerId) !== actorId) return reject("The key in the publicKey does not agree with the one in the assertionMethod.");
117
134
  }
118
- if (publicKey.publicKey == null || !await isSamePublicKey(publicKey.publicKey, multikey.publicKey) || publicKey.ownerId != null && getCanonicalPortableId(publicKey.ownerId) !== actorId) return reject("The key in the publicKey does not agree with the one in the assertionMethod.");
135
+ } else {
136
+ if (publicKeyNodes.length < 1) return reject("The actor document embeds the key in neither its assertionMethod nor its publicKey.");
137
+ const embedded = await parseEmbeddedPublicKey(publicKeyNodes[0]);
138
+ if (embedded == null) return reject("The key in the publicKey is not embedded or malformed.");
139
+ if (embedded.ownerId == null || getCanonicalPortableId(embedded.ownerId) !== actorId) return reject("The owner of the key is not the actor.");
140
+ publicKey = embedded.publicKey;
119
141
  }
120
142
  if (!actor.gateways.some((gateway) => isGatewayUrl(gateway) && gateway.origin === keyId.origin)) return reject(`The gateway ${keyId.origin} is not listed in the actor's gateways.`);
121
143
  const key = new CryptographicKey({
122
144
  id: keyId,
123
145
  owner: actor.id,
124
- publicKey: multikey.publicKey
146
+ publicKey
125
147
  });
126
148
  return {
127
149
  type: "verified",
@@ -171,24 +193,21 @@ async function fetchPortableGatewayKey(keyId, options = {}) {
171
193
  contextLoader
172
194
  });
173
195
  }
174
- function findEmbeddedNode(values, id) {
175
- if (!Array.isArray(values)) return null;
196
+ function findNodes(values, id) {
197
+ if (!Array.isArray(values)) return [];
198
+ const nodes = [];
176
199
  for (const value of values) {
177
200
  if (value == null || typeof value !== "object" || Array.isArray(value)) continue;
178
201
  const node = value;
179
202
  const nodeId = node["@id"];
180
- if (typeof nodeId !== "string") continue;
181
- let parsed;
182
- try {
183
- parsed = new URL(nodeId);
184
- } catch {
185
- continue;
186
- }
187
- if (parsed.href !== id.href) continue;
188
- if (Object.keys(node).every((key) => key === "@id")) continue;
189
- return node;
203
+ if (typeof nodeId !== "string" || !URL.canParse(nodeId)) continue;
204
+ if (new URL(nodeId).href === id.href) nodes.push(node);
190
205
  }
191
- return null;
206
+ return nodes;
207
+ }
208
+ /** A node with nothing but an `@id` is a reference, not an embedded key. */
209
+ function isReference(node) {
210
+ return Object.keys(node).every((key) => key === "@id");
192
211
  }
193
212
  //#endregion
194
213
  export { verifyPortableGatewayKeyDocument as n, fetchPortableGatewayKey as t };
@@ -0,0 +1,136 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { isActor } from "@fedify/vocab";
5
+ import { canonicalizePortableUri, formatIri, fromCompatibleEf61Id, getFe34Origin, toCompatibleEf61Id } from "@fedify/vocab-runtime";
6
+ import { isCompatibleEf61Iri } from "@fedify/vocab-runtime/internal/portable-dereference";
7
+ //#region src/sig/portable-key-id.ts
8
+ const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
9
+ /**
10
+ * Checks whether the URL is an `ap:` or `ap+ef61:` URI.
11
+ * @internal
12
+ */
13
+ function isPortableUri(url) {
14
+ return url.protocol === "ap:" || url.protocol === "ap+ef61:";
15
+ }
16
+ /**
17
+ * Checks whether an ID identifies an [FEP-ef61] portable object, i.e., it is
18
+ * an `ap:` or `ap+ef61:` URI, or looks like a compatible identifier.
19
+ * A malformed compatible identifier counts too, so that it is rejected as
20
+ * a portable object that no proof can match, rather than trusted by its web
21
+ * origin.
22
+ *
23
+ * [FEP-ef61]: https://w3id.org/fep/ef61
24
+ * @internal
25
+ */
26
+ function isPortableId(id) {
27
+ if (typeof id === "string") {
28
+ if (/^ap(?:\+ef61)?:\/\//i.test(id)) return true;
29
+ } else if (isPortableUri(id)) return true;
30
+ return isCompatibleEf61Iri(id);
31
+ }
32
+ /**
33
+ * Gets the DID, i.e., the FEP-fe34 cryptographic origin, of an `ap:` or
34
+ * `ap+ef61:` URI, a DID URL, or a compatible identifier.
35
+ * @returns The DID, or `null` if the ID is none of them, or is malformed.
36
+ * @internal
37
+ */
38
+ function getPortableDid(id) {
39
+ try {
40
+ const raw = typeof id === "string" ? id : id.href;
41
+ if (/^(?:did:|ap(?:\+ef61)?:\/\/)/i.test(raw)) return getFe34Origin(id);
42
+ const portable = fromCompatibleEf61Id(id);
43
+ return portable == null ? null : getFe34Origin(portable);
44
+ } catch (error) {
45
+ if (error instanceof TypeError) return null;
46
+ throw error;
47
+ }
48
+ }
49
+ /**
50
+ * Checks whether an activity is performed by an FEP-ef61 portable actor,
51
+ * i.e., any of its actors has an `ap:` or `ap+ef61:` ID, or a compatible
52
+ * identifier.
53
+ * @internal
54
+ */
55
+ function hasPortableActor(activity) {
56
+ return activity.actorIds.some((id) => isPortableId(id));
57
+ }
58
+ /**
59
+ * Checks whether a key ID looks like an [FEP-ef61] compatible identifier,
60
+ * i.e., an HTTP(S) URL under a gateway's `/.well-known/apgateway/did:` path.
61
+ * Such a key ID names a key that a gateway holds for a portable actor, so it
62
+ * is resolved only as a gateway key, and never cached in a key cache.
63
+ *
64
+ * [FEP-ef61]: https://w3id.org/fep/ef61
65
+ * @internal
66
+ */
67
+ function isCompatibleKeyId(keyId) {
68
+ return (keyId.protocol === "http:" || keyId.protocol === "https:") && COMPATIBLE_KEY_ID_PATH_PATTERN.test(keyId.pathname);
69
+ }
70
+ /**
71
+ * Gets the canonical portable ID of an actor ID that is either an `ap:` or
72
+ * `ap+ef61:` URI or a compatible identifier.
73
+ * @returns The canonical portable ID, or `null` if the ID is neither, or is
74
+ * malformed.
75
+ * @internal
76
+ */
77
+ function getCanonicalPortableId(id) {
78
+ try {
79
+ if (isPortableUri(id)) return canonicalizePortableUri(formatIri(id));
80
+ const portable = fromCompatibleEf61Id(id);
81
+ if (portable == null) return null;
82
+ return canonicalizePortableUri(formatIri(portable));
83
+ } catch (error) {
84
+ if (error instanceof TypeError) return null;
85
+ throw error;
86
+ }
87
+ }
88
+ /**
89
+ * Builds the base of the key IDs of a gateway's keys for a portable actor:
90
+ * the actor's compatible identifier on the gateway.
91
+ * @param actorId The portable actor's ID, either an `ap:` or `ap+ef61:` URI
92
+ * or a compatible identifier. It must not have a fragment.
93
+ * @param gateway The gateway's origin, e.g., `https://example.com`.
94
+ * @returns The compatible identifier without query and fragment.
95
+ * @throws {TypeError} If the actor ID is not a portable ID, or has
96
+ * a fragment.
97
+ * @internal
98
+ */
99
+ function getGatewayKeyBase(actorId, gateway) {
100
+ let portable;
101
+ if (isPortableUri(actorId)) portable = actorId;
102
+ else try {
103
+ portable = fromCompatibleEf61Id(actorId);
104
+ } catch (error) {
105
+ if (!(error instanceof TypeError)) throw error;
106
+ portable = null;
107
+ }
108
+ if (portable == null) throw new TypeError(`The portable actor ID ${actorId.href} must be an ap: or ap+ef61: URI, or an FEP-ef61 compatible identifier.`);
109
+ if (portable.hash !== "") throw new TypeError(`The portable actor ID ${actorId.href} must not have a fragment.`);
110
+ const base = toCompatibleEf61Id(portable, gateway);
111
+ base.search = "";
112
+ base.hash = "";
113
+ return base;
114
+ }
115
+ /**
116
+ * Tells whether a parsed document is a portable actor, i.e., an actor whose
117
+ * ID is an `ap:` or `ap+ef61:` URI or a compatible identifier. Such an actor
118
+ * is never authenticated by the web origin that served it; at a compatible
119
+ * key ID, it makes the key a gateway key that only
120
+ * {@link verifyPortableGatewayKeyDocument} can vouch for.
121
+ * @internal
122
+ */
123
+ function isPortableActorDocument(object) {
124
+ return isActor(object) && object.id != null && isPortableId(object.id);
125
+ }
126
+ /**
127
+ * Compares two public keys by their key material.
128
+ * @internal
129
+ */
130
+ async function isSamePublicKey(a, b) {
131
+ if (a.algorithm.name !== b.algorithm.name) return false;
132
+ const [x, y] = await Promise.all([crypto.subtle.exportKey("jwk", a), crypto.subtle.exportKey("jwk", b)]);
133
+ return x.kty === y.kty && x.crv === y.crv && x.n === y.n && x.e === y.e && x.x === y.x && x.y === y.y;
134
+ }
135
+ //#endregion
136
+ export { isCompatibleKeyId as a, isPortableUri as c, hasPortableActor as i, isSamePublicKey as l, getGatewayKeyBase as n, isPortableActorDocument as o, getPortableDid as r, isPortableId as s, getCanonicalPortableId as t };
@@ -1,16 +1,17 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Dn-py9Zy.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-Beod6tzO.mjs";
6
- import { r as fetchKey, s as validateCryptoKey } from "./key-BJrp524f.mjs";
4
+ import { n as version, t as name } from "./deno-8t8k_6Yv.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-B7YwKTFr.mjs";
6
+ import { r as getPortableDid, s as isPortableId } from "./portable-key-id-Bva5iT3W.mjs";
7
+ import { r as fetchKey, s as validateCryptoKey } from "./key-Dwm4VswT.mjs";
7
8
  import { n as preloadedOnlyDocumentLoader } from "./public-audience--klBIOk1.mjs";
8
9
  import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-4IJqPYos.mjs";
9
- import { c as getNormalizationContextLoader } from "./ld-BmFz5NLA.mjs";
10
+ import { c as getNormalizationContextLoader } from "./ld-BYZOoDzZ.mjs";
10
11
  import { getLogger } from "@logtape/logtape";
11
12
  import { Activity, DataIntegrityProof, Multikey, getTypeId } from "@fedify/vocab";
12
13
  import { SpanStatusCode, trace } from "@opentelemetry/api";
13
- import { encodeMultibase, formatIri, getDocumentLoader, getFe34Origin, haveSameFe34Origin, parseIri } from "@fedify/vocab-runtime";
14
+ import { encodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, parseIri } from "@fedify/vocab-runtime";
14
15
  import { encodeHex } from "byte-encodings/hex";
15
16
  import { isPlainJsonTree, retainSignedRepresentation } from "@fedify/vocab-runtime/internal/signed-representation";
16
17
  import jsonld from "@fedify/vocab-runtime/jsonld";
@@ -736,8 +737,37 @@ async function expandPortableObjectRoot(jsonLd, contextLoader) {
736
737
  }
737
738
  };
738
739
  }
740
+ /**
741
+ * Gets the DID of a portable object ID, which is either an `ap:` or
742
+ * `ap+ef61:` URI or an FEP-ef61 compatible identifier.
743
+ * @throws {TypeError} If the ID is malformed.
744
+ */
745
+ function getObjectDid(id) {
746
+ try {
747
+ parseIri(id);
748
+ if (PORTABLE_OBJECT_ID_PATTERN.test(id)) return getFe34Origin(id);
749
+ const portableId = fromCompatibleEf61Id(id);
750
+ if (portableId != null) return getFe34Origin(portableId);
751
+ } catch (error) {
752
+ if (!(error instanceof TypeError)) throw error;
753
+ throw new InvalidPortableObjectIdError(id, { cause: error });
754
+ }
755
+ throw new InvalidPortableObjectIdError(id);
756
+ }
757
+ /**
758
+ * The error thrown when a document's ID looks portable, i.e., it is an `ap:`
759
+ * or `ap+ef61:` URI or looks like an FEP-ef61 compatible identifier, but is
760
+ * malformed.
761
+ * @internal
762
+ */
763
+ var InvalidPortableObjectIdError = class extends TypeError {
764
+ constructor(id, options) {
765
+ super(`Invalid portable object ID: ${id}`, options);
766
+ this.name = "InvalidPortableObjectIdError";
767
+ }
768
+ };
739
769
  async function preparePortableObjectProof(jsonLd, options) {
740
- if (isJsonLdNode(jsonLd) && typeof jsonLd["@id"] === "string" && !PORTABLE_OBJECT_ID_PATTERN.test(jsonLd["@id"])) return {
770
+ if (isJsonLdNode(jsonLd) && typeof jsonLd["@id"] === "string" && !isPortableId(jsonLd["@id"])) return {
741
771
  prepared: false,
742
772
  result: {
743
773
  verified: false,
@@ -746,15 +776,15 @@ async function preparePortableObjectProof(jsonLd, options) {
746
776
  };
747
777
  const { root, proofContextLoader } = await expandPortableObjectRoot(jsonLd, options.contextLoader);
748
778
  const id = root["@id"];
749
- if (typeof id !== "string" || !PORTABLE_OBJECT_ID_PATTERN.test(id)) return {
779
+ if (typeof id !== "string" || !isPortableId(id)) return {
750
780
  prepared: false,
751
781
  result: {
752
782
  verified: false,
753
783
  reason: { type: "notPortableObject" }
754
784
  }
755
785
  };
786
+ const objectDid = getObjectDid(id);
756
787
  const objectId = parseIri(id);
757
- getFe34Origin(objectId);
758
788
  const objectType = classifyFep2277CoreType(root);
759
789
  if (objectType === "verificationMethod" || objectType === "publicKey" || objectType === "link") return {
760
790
  prepared: false,
@@ -859,7 +889,7 @@ async function preparePortableObjectProof(jsonLd, options) {
859
889
  }
860
890
  };
861
891
  }
862
- if (!haveSameFe34Origin(objectId, verificationMethod)) return {
892
+ if (getFe34Origin(verificationMethod) !== objectDid) return {
863
893
  prepared: false,
864
894
  result: {
865
895
  verified: false,
@@ -918,15 +948,24 @@ async function verifyPortableObjectProofPolicy(jsonLd, key, options = {}) {
918
948
  * collection without a proof is reported separately so a caller can apply a
919
949
  * gateway trust policy. Embedded portable objects are not traversed.
920
950
  *
951
+ * A portable object is a document whose ID is an `ap:` or `ap+ef61:` URI, or
952
+ * an [FEP-ef61] compatible identifier such as
953
+ * `https://gw.example/.well-known/apgateway/did:key:z6Mk…/actor`, which
954
+ * stands for the portable ID `ap+ef61://did:key:z6Mk…/actor`.
955
+ *
921
956
  * Every proof must use a DID URL whose DID matches the portable object's
922
- * authority, and every proof must pass {@link verifyProof}.
957
+ * authority, i.e., the DID of its canonical portable ID, and every proof must
958
+ * pass {@link verifyProof}. The proofs are verified over the document as
959
+ * given; a compatible identifier is never rewritten into a portable ID.
960
+ *
961
+ * [FEP-ef61]: https://w3id.org/fep/ef61
923
962
  *
924
963
  * @param jsonLd The JSON-LD document to verify.
925
964
  * @param options Additional options. See also
926
965
  * {@link VerifyPortableObjectProofOptions}.
927
966
  * @returns The detailed portable proof-policy result.
928
967
  * @throws {TypeError} If the input is not a single JSON-LD object or has a
929
- * malformed portable ID.
968
+ * malformed portable ID or compatible identifier.
930
969
  * @since 2.4.0
931
970
  */
932
971
  async function verifyPortableObjectProof(jsonLd, options = {}) {
@@ -1026,7 +1065,7 @@ async function verifyObject(cls, jsonLd, options = {}) {
1026
1065
  };
1027
1066
  const proofDocumentLoader = async (url, loaderOptions) => {
1028
1067
  const remoteDocument = await baseDocumentLoader(url, loaderOptions);
1029
- if (PORTABLE_OBJECT_ID_PATTERN.test(url)) loadedPortableProofs++;
1068
+ if (isPortableId(url)) loadedPortableProofs++;
1030
1069
  const reference = normalizeDocumentUrl(url);
1031
1070
  const candidateIndex = rawProofCandidates.findIndex((candidate, index) => !hydratedCandidates.has(index) && !rawProofCandidatePool.used.has(index) && candidate.reference === reference);
1032
1071
  if (candidateIndex >= 0) {
@@ -1076,14 +1115,16 @@ async function verifyObject(cls, jsonLd, options = {}) {
1076
1115
  return object;
1077
1116
  }
1078
1117
  function deleteAuthenticatedAttribution(attributions, controllerId, verificationMethodId) {
1079
- const controllerHasCryptographicOrigin = hasCryptographicOrigin(controllerId.href);
1080
- const verificationMethodMatchesController = controllerHasCryptographicOrigin && hasCryptographicOrigin(verificationMethodId.href) && haveSameFe34Origin(controllerId, verificationMethodId);
1081
- if (!controllerHasCryptographicOrigin || verificationMethodMatchesController) attributions.delete(controllerId.href);
1082
- if (!verificationMethodMatchesController) return;
1083
- for (const attribution of [...attributions]) if (hasCryptographicOrigin(attribution) && haveSameFe34Origin(controllerId, attribution)) attributions.delete(attribution);
1118
+ if (!hasCryptographicOrigin(controllerId.href) && !isPortableId(controllerId)) {
1119
+ attributions.delete(controllerId.href);
1120
+ return;
1121
+ }
1122
+ const did = getPortableDid(controllerId);
1123
+ if (did == null || !hasCryptographicOrigin(verificationMethodId.href) || getPortableDid(verificationMethodId) !== did) return;
1124
+ for (const attribution of [...attributions]) if ((hasCryptographicOrigin(attribution) || isPortableId(attribution)) && getPortableDid(attribution) === did) attributions.delete(attribution);
1084
1125
  }
1085
1126
  function hasCryptographicOrigin(iri) {
1086
1127
  return /^did:/i.test(iri) || /^ap(?:\+ef61)?:\/\//i.test(iri);
1087
1128
  }
1088
1129
  //#endregion
1089
- export { verifyObject as a, verifyPortableObjectProofWithRoot as c, verifyMapLocalProof as i, verifyProof as l, hasProofLike as n, verifyPortableObjectProof as o, signObject as r, verifyPortableObjectProofPolicy as s, createProof as t };
1130
+ export { verifyMapLocalProof as a, verifyPortableObjectProofPolicy as c, signObject as i, verifyPortableObjectProofWithRoot as l, createProof as n, verifyObject as o, hasProofLike as r, verifyPortableObjectProof as s, InvalidPortableObjectIdError as t, verifyProof as u };
@@ -1,9 +1,9 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Dn-py9Zy.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics } from "./metrics-Beod6tzO.mjs";
6
- import { n as doubleKnock } from "./http-B0VRNipE.mjs";
4
+ import { n as version, t as name } from "./deno-8t8k_6Yv.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics } from "./metrics-B7YwKTFr.mjs";
6
+ import { n as doubleKnock } from "./http-B2ftY0Ha.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
9
9
  import { FetchError, UrlError, validatePublicUrl } from "@fedify/vocab-runtime";
@@ -3,7 +3,7 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
5
5
  import "../std__assert-DvMDRPes.mjs";
6
- import { i as inspectCompoundPortableObjectApplicability, n as discoverCompoundProofDocuments } from "../compound-proof-B6Aj2kGY.mjs";
6
+ import { i as inspectCompoundPortableObjectApplicability, n as discoverCompoundProofDocuments } from "../compound-proof-CTx_lewQ.mjs";
7
7
  import { t as map_local_create_note_default } from "../map-local-create-note-B0SeXJms.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  //#region src/sig/compound-proof-discovery.test.ts
@@ -3,7 +3,7 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
5
5
  import "../std__assert-DvMDRPes.mjs";
6
- import { i as inspectCompoundPortableObjectApplicability, r as findUnsupportedCompoundProofShape, t as containsCompoundPortableObject } from "../compound-proof-B6Aj2kGY.mjs";
6
+ import { i as inspectCompoundPortableObjectApplicability, r as findUnsupportedCompoundProofShape, t as containsCompoundPortableObject } from "../compound-proof-CTx_lewQ.mjs";
7
7
  import { test } from "@fedify/fixture";
8
8
  //#region src/sig/compound-proof-producer.test.ts
9
9
  const portableId = "ap+ef61://did:key:z6MkAlice/objects/1";