@fedify/fedify 2.4.0-dev.2122 → 2.4.0-dev.2129

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 (98) hide show
  1. package/dist/{builder-BCXywsZF.mjs → builder-C0bxZDL0.mjs} +3 -3
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{compound-proof-Fxd45Oy5.mjs → compound-proof-D9Kljk_7.mjs} +3 -3
  6. package/dist/{context-tOiJfBoD.d.ts → context-Cz1w5Krc.d.ts} +31 -5
  7. package/dist/{context-CoNRxGYp.mjs → context-DfSVPcJ8.mjs} +6 -3
  8. package/dist/{context-SMNOiiY6.d.cts → context-Dvec_D_h.d.cts} +31 -5
  9. package/dist/{deno-BZrnW0Qz.mjs → deno-_o3lkDlG.mjs} +1 -1
  10. package/dist/{docloader-3BHaS20Z.mjs → docloader-BbS0rO-Y.mjs} +7 -7
  11. package/dist/federation/builder.test.mjs +1 -1
  12. package/dist/federation/compound-fanout.test.mjs +3 -3
  13. package/dist/federation/handler-compound-proof.test.mjs +4 -4
  14. package/dist/federation/handler.test.mjs +6 -6
  15. package/dist/federation/hashlink-media.test.mjs +2 -2
  16. package/dist/federation/idempotency.test.mjs +2 -2
  17. package/dist/federation/metrics.test.mjs +1 -1
  18. package/dist/federation/middleware.test.mjs +8 -8
  19. package/dist/federation/mod.cjs +1 -1
  20. package/dist/federation/mod.d.cts +2 -2
  21. package/dist/federation/mod.d.ts +2 -2
  22. package/dist/federation/mod.js +1 -1
  23. package/dist/federation/outgoing-proof.test.mjs +6 -6
  24. package/dist/federation/portable-actor.test.mjs +3 -3
  25. package/dist/federation/portable-chain.test.d.mts +2 -0
  26. package/dist/federation/portable-chain.test.mjs +358 -0
  27. package/dist/federation/portable-gateway-keys.test.mjs +77 -6
  28. package/dist/federation/portable-inbox.test.mjs +9 -4
  29. package/dist/federation/portable.test.mjs +3 -3
  30. package/dist/federation/send.test.mjs +3 -3
  31. package/dist/federation/tasks/enqueue.test.mjs +2 -2
  32. package/dist/federation/tasks/tasks.test.mjs +3 -3
  33. package/dist/federation/temporal.test.mjs +1 -1
  34. package/dist/federation/webfinger.test.mjs +2 -2
  35. package/dist/{federation-Ml6NrgRV.cjs → federation-DRY8KrkB.cjs} +46 -22
  36. package/dist/{http-4bmgw-mA.cjs → http-B1J6E-bT.cjs} +384 -33
  37. package/dist/{http-B9BQDNIy.mjs → http-BpOiZE09.mjs} +18 -12
  38. package/dist/{http-D76Py0M4.d.cts → http-C-x_OLSp.d.ts} +57 -14
  39. package/dist/{http-BETASGl0.js → http-C2M6wcs8.js} +374 -35
  40. package/dist/{http-BrQgkJay.d.ts → http-CjezPjFX.d.cts} +57 -14
  41. package/dist/{key-PyYvun0F.mjs → key-DolJjJab.mjs} +103 -16
  42. package/dist/{kv-cache-CPqUxRL3.mjs → kv-cache-Bq2cah0c.mjs} +1 -1
  43. package/dist/{kv-cache-C4mv2ZFQ.js → kv-cache-D7TiyMxv.js} +6 -6
  44. package/dist/{kv-cache-D2Bz6t_c.cjs → kv-cache-D9nRbG2Q.cjs} +6 -6
  45. package/dist/{ld-u4JOD4iO.mjs → ld-Dwc176V4.mjs} +3 -3
  46. package/dist/{metrics-DCLoLvLz.mjs → metrics-CDaHfBXY.mjs} +1 -1
  47. package/dist/{middleware-B7bmtONC.mjs → middleware-CmB1FD8A.mjs} +1 -1
  48. package/dist/{middleware-DWlAGlyP.js → middleware-CylX-pvI.js} +52 -28
  49. package/dist/{middleware-CgOqRkK-.mjs → middleware-DN3MdTDg.mjs} +64 -39
  50. package/dist/{mod-CTh7ijx5.d.ts → mod-Bz9tFXI2.d.ts} +14 -3
  51. package/dist/{mod-Cwdt_CWU.d.ts → mod-CZetqT5h.d.ts} +1 -1
  52. package/dist/{mod-Cr79cQlc.d.cts → mod-D4g9ffEn.d.cts} +1 -1
  53. package/dist/{mod-DnElMslb.d.cts → mod-DGOIxEfb.d.cts} +1 -1
  54. package/dist/{mod-DMdGFMEl.d.cts → mod-S--JxYjo.d.cts} +14 -3
  55. package/dist/{mod-COWgSo2v.d.ts → mod-owwElvpw.d.ts} +1 -1
  56. package/dist/mod.cjs +8 -8
  57. package/dist/mod.d.cts +5 -5
  58. package/dist/mod.d.ts +5 -5
  59. package/dist/mod.js +5 -5
  60. package/dist/nodeinfo/handler.test.mjs +2 -2
  61. package/dist/{owner-CPFM-sqo.mjs → owner-BNHhwjNu.mjs} +37 -4
  62. package/dist/{sig-C1xOA4ml.js → portable-collection-CRpTUHCY.mjs} +5 -4
  63. package/dist/portable-collection-CgglAI9B.js +656 -0
  64. package/dist/{sig-Cb9VSeiF.cjs → portable-collection-PTGD0q70.cjs} +253 -2
  65. package/dist/portable-key-DqX2emHQ.mjs +395 -0
  66. package/dist/{portable-key-id-T7lecIbt.mjs → portable-key-id-Bg259c4Z.mjs} +69 -2
  67. package/dist/{proof-BCOg7Njv.mjs → proof-B4_0E-KU.mjs} +5 -5
  68. package/dist/{send-C8eOPat3.mjs → send-Dpu5qbP5.mjs} +3 -3
  69. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  70. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  71. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  72. package/dist/sig/compound-proof.test.mjs +2 -2
  73. package/dist/sig/http.test.mjs +2 -2
  74. package/dist/sig/key.test.mjs +1 -1
  75. package/dist/sig/ld.test.mjs +2 -2
  76. package/dist/sig/mod.cjs +5 -6
  77. package/dist/sig/mod.d.cts +2 -2
  78. package/dist/sig/mod.d.ts +2 -2
  79. package/dist/sig/mod.js +2 -3
  80. package/dist/sig/owner.test.mjs +1 -1
  81. package/dist/sig/portable-collection.test.mjs +20 -431
  82. package/dist/sig/portable-key.test.mjs +479 -5
  83. package/dist/sig/proof.test.mjs +3 -3
  84. package/dist/{temporal-vjHOhBpy.mjs → temporal-78JVR3GK.mjs} +1 -1
  85. package/dist/testing/kv.test.mjs +1 -1
  86. package/dist/testing/mod.d.mts +30 -4
  87. package/dist/testing/mod.mjs +2 -2
  88. package/dist/{testing-BWq1dCzx.mjs → testing-otQhu4aD.mjs} +1 -1
  89. package/dist/utils/docloader.test.mjs +81 -2
  90. package/dist/utils/kv-cache.test.mjs +1 -1
  91. package/dist/utils/mod.cjs +1 -1
  92. package/dist/utils/mod.d.cts +1 -1
  93. package/dist/utils/mod.d.ts +1 -1
  94. package/dist/utils/mod.js +1 -1
  95. package/package.json +7 -7
  96. package/dist/owner-DkA0Lz7v.cjs +0 -225
  97. package/dist/owner-wNinn7RK.js +0 -215
  98. package/dist/portable-key-DyylakVV.mjs +0 -214
@@ -1,11 +1,250 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http-4bmgw-mA.cjs");
3
- require("./owner-DkA0Lz7v.cjs");
2
+ const require_http = require("./http-B1J6E-bT.cjs");
4
3
  let _logtape_logtape = require("@logtape/logtape");
5
4
  let _fedify_vocab = require("@fedify/vocab");
6
5
  let _opentelemetry_api = require("@opentelemetry/api");
7
6
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
8
7
  let _fedify_vocab_runtime_internal_portable_dereference = require("@fedify/vocab-runtime/internal/portable-dereference");
8
+ //#region src/sig/owner.ts
9
+ const logger$1 = (0, _logtape_logtape.getLogger)([
10
+ "fedify",
11
+ "sig",
12
+ "owner"
13
+ ]);
14
+ /**
15
+ * Checks if the actor of the given activity owns the specified key.
16
+ *
17
+ * Ownership is never taken from what the activity or the key say about
18
+ * themselves, as both are written by whoever sent the activity. It is
19
+ * established from a document that the owner itself serves: either the key's
20
+ * claimed owner links back to the key, or the actor the activity claims lists
21
+ * the key as its own.
22
+ *
23
+ * @param activity The activity to check.
24
+ * @param key The public key to check.
25
+ * @param options Options for checking the key ownership.
26
+ * @returns Whether the actor is the owner of the key.
27
+ */
28
+ async function doesActorOwnKey(activity, key, options) {
29
+ return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(require_http.name, require_http.version).startActiveSpan("activitypub.verify_key_ownership", {
30
+ kind: _opentelemetry_api.SpanKind.INTERNAL,
31
+ attributes: {
32
+ "activitypub.actor.id": activity.actorId?.href ?? "",
33
+ "activitypub.key.id": key.id?.href ?? ""
34
+ }
35
+ }, async (span) => {
36
+ try {
37
+ const actorId = activity.actorId;
38
+ if (actorId == null) {
39
+ span.setAttribute("activitypub.key_ownership.verified", false);
40
+ span.setAttribute("activitypub.key_ownership.method", "none");
41
+ return false;
42
+ }
43
+ if (key.id != null && require_http.isPortableUri(key.id)) {
44
+ const owner = await getPortableActorKeyOwner(key, options);
45
+ const ownerId = owner?.id == null ? null : require_http.getCanonicalPortableId(owner.id);
46
+ const verified = ownerId != null && ownerId === require_http.getCanonicalPortableId(actorId);
47
+ span.setAttribute("activitypub.key_ownership.verified", verified);
48
+ span.setAttribute("activitypub.key_ownership.method", "portable_actor_key");
49
+ return verified;
50
+ }
51
+ if (key.id != null && require_http.isCompatibleKeyId(key.id)) {
52
+ const owner = await getPortableGatewayKeyOwner(key, options);
53
+ if (owner !== void 0) {
54
+ const ownerId = owner?.id == null ? null : require_http.getCanonicalPortableId(owner.id);
55
+ const verified = ownerId != null && ownerId === require_http.getCanonicalPortableId(actorId);
56
+ span.setAttribute("activitypub.key_ownership.verified", verified);
57
+ span.setAttribute("activitypub.key_ownership.method", "portable_gateway_key");
58
+ return verified;
59
+ }
60
+ }
61
+ if (key.ownerId != null) {
62
+ const owner = await require_http.verifyKeyOwnership(key, options);
63
+ if (owner?.id != null && owner.id.href === actorId.href) {
64
+ span.setAttribute("activitypub.key_ownership.verified", true);
65
+ span.setAttribute("activitypub.key_ownership.method", "key_owner");
66
+ return true;
67
+ }
68
+ }
69
+ const actor = await require_http.fetchActorDocument(actorId, options);
70
+ if (actor != null && await doesActorListKey(actor, key, options)) {
71
+ span.setAttribute("activitypub.key_ownership.verified", true);
72
+ span.setAttribute("activitypub.key_ownership.method", "actor_fetch");
73
+ return true;
74
+ }
75
+ logger$1.debug("The actor {actorId} does not own key {keyId}: neither the owner the key claims nor the actor's own document links the two to each other.", {
76
+ actorId: actorId.href,
77
+ keyId: key.id?.href
78
+ });
79
+ span.setAttribute("activitypub.key_ownership.verified", false);
80
+ span.setAttribute("activitypub.key_ownership.method", "actor_fetch");
81
+ return false;
82
+ } catch (error) {
83
+ span.recordException(error);
84
+ span.setStatus({
85
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
86
+ message: String(error)
87
+ });
88
+ throw error;
89
+ } finally {
90
+ span.end();
91
+ }
92
+ });
93
+ }
94
+ /**
95
+ * Determines whether the given actor's own document lists the given key among
96
+ * its public keys.
97
+ *
98
+ * Keys are matched by id, which is what an actor document links to. A key
99
+ * document may leave its id implicit, though, and then there is no id for the
100
+ * actor to link to; the key material itself is the only thing the two can
101
+ * have in common, so that is what gets compared.
102
+ */
103
+ async function doesActorListKey(actor, key, options) {
104
+ if (key.id != null) {
105
+ for (const publicKeyId of actor.publicKeyIds) if (publicKeyId.href === key.id.href) return true;
106
+ return false;
107
+ }
108
+ if (key.publicKey == null) return false;
109
+ const publicKeys = actor.getPublicKeys({
110
+ ...options,
111
+ suppressError: true
112
+ });
113
+ for await (const publicKey of publicKeys) {
114
+ if (publicKey.publicKey == null) continue;
115
+ if (await isSamePublicKey(publicKey.publicKey, key.publicKey)) return true;
116
+ }
117
+ return false;
118
+ }
119
+ /**
120
+ * Compares two public keys by their key material, ignoring the metadata that
121
+ * surrounds it.
122
+ */
123
+ async function isSamePublicKey(a, b) {
124
+ const [x, y] = await Promise.all([require_http.exportJwk(a), require_http.exportJwk(b)]);
125
+ 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;
126
+ }
127
+ /**
128
+ * Gets the actor that owns the specified key. Returns `null` if the key has no
129
+ * known owner.
130
+ *
131
+ * The owner is only returned when the key and the owner link to each other:
132
+ * a document that merely claims to own a key is not enough, as the claim and
133
+ * the key can come from the same host.
134
+ *
135
+ * @param keyId The ID of the key to check, or the key itself.
136
+ * @param options Options for getting the key owner.
137
+ * @returns The actor that owns the key, or `null` if the key has no known
138
+ * owner.
139
+ * @since 0.7.0
140
+ */
141
+ async function getKeyOwner(keyId, options) {
142
+ const tracerProvider = options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
143
+ const documentLoader = options.documentLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)();
144
+ const fetchOptions = {
145
+ documentLoader,
146
+ contextLoader: options.contextLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)(),
147
+ tracerProvider
148
+ };
149
+ const id = keyId instanceof _fedify_vocab.CryptographicKey ? keyId.id : keyId;
150
+ if (id != null && require_http.isPortableUri(id)) return await getPortableActorKeyOwner(keyId, fetchOptions);
151
+ if (id != null && require_http.isCompatibleKeyId(id)) {
152
+ const owner = await getPortableGatewayKeyOwner(keyId, fetchOptions);
153
+ if (owner !== void 0) return owner;
154
+ }
155
+ if (keyId instanceof _fedify_vocab.CryptographicKey) return await require_http.verifyKeyOwnership(keyId, fetchOptions);
156
+ let keyDoc;
157
+ let documentUrl = keyId;
158
+ try {
159
+ const remoteDocument = await documentLoader(keyId.href);
160
+ keyDoc = remoteDocument.document;
161
+ documentUrl = new URL(remoteDocument.documentUrl ?? "", keyId);
162
+ } catch (_) {
163
+ return null;
164
+ }
165
+ let object;
166
+ try {
167
+ object = await _fedify_vocab.Object.fromJsonLd(keyDoc, {
168
+ ...fetchOptions,
169
+ baseUrl: documentUrl
170
+ });
171
+ } catch (e) {
172
+ if (!(e instanceof TypeError)) throw e;
173
+ try {
174
+ object = await _fedify_vocab.CryptographicKey.fromJsonLd(keyDoc, fetchOptions);
175
+ } catch (e) {
176
+ if (e instanceof TypeError) return null;
177
+ throw e;
178
+ }
179
+ }
180
+ if (object instanceof _fedify_vocab.CryptographicKey) {
181
+ if (object.id != null && object.id.href !== keyId.href) return null;
182
+ return await require_http.verifyKeyOwnership(object.id == null ? object.clone({ id: keyId }) : object, fetchOptions);
183
+ }
184
+ if (!(0, _fedify_vocab.isActor)(object)) return null;
185
+ if (object.id == null || require_http.isPortableId(object.id) || object.id.origin !== documentUrl.origin) {
186
+ logger$1.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}.", {
187
+ documentUrl: documentUrl.href,
188
+ actorId: object.id?.href,
189
+ keyId: keyId.href
190
+ });
191
+ return null;
192
+ }
193
+ for (const kid of object.publicKeyIds) if (kid.href === keyId.href) return object;
194
+ return null;
195
+ }
196
+ /**
197
+ * Resolves the owner of a key at a compatible identifier, if the key turns
198
+ * out to be a gateway key of an [FEP-ef61] portable actor.
199
+ *
200
+ * [FEP-ef61]: https://w3id.org/fep/ef61
201
+ * @param key The key, or its ID, which must be a compatible identifier.
202
+ * @param options Options for fetching the key.
203
+ * @returns `undefined` if the key is not a gateway key, i.e., the document at
204
+ * the key ID is not a portable actor, so the usual checks apply.
205
+ * Otherwise, the portable actor if its signed document vouches for
206
+ * the key, and the given key, if any, has the same key material, or
207
+ * `null` if not.
208
+ */
209
+ async function getPortableGatewayKeyOwner(key, options) {
210
+ const keyId = key instanceof _fedify_vocab.CryptographicKey ? key.id : key;
211
+ if (keyId == null) return void 0;
212
+ const resolution = await require_http.fetchPortableGatewayKey(keyId, options);
213
+ if (resolution.type === "legacy") return void 0;
214
+ if (resolution.type === "rejected") return null;
215
+ if (key instanceof _fedify_vocab.CryptographicKey) {
216
+ if (key.publicKey == null || !await require_http.isSamePublicKey(key.publicKey, resolution.key.publicKey)) {
217
+ logger$1.debug("The key {keyId} does not have the same key material as the gateway key its portable actor vouches for.", { keyId: keyId.href });
218
+ return null;
219
+ }
220
+ }
221
+ return resolution.actor;
222
+ }
223
+ /**
224
+ * Resolves the owner of a key of an [FEP-ef61] portable actor at an `ap:` or
225
+ * `ap+ef61:` key ID.
226
+ *
227
+ * [FEP-ef61]: https://w3id.org/fep/ef61
228
+ * @param key The key, or its ID, which must be an `ap:` or `ap+ef61:` URI.
229
+ * @param options Options for fetching the key.
230
+ * @returns The portable actor if its signed document vouches for the key,
231
+ * and the given key, if any, has the same key material, or `null`
232
+ * if not.
233
+ */
234
+ async function getPortableActorKeyOwner(key, options) {
235
+ const keyId = key instanceof _fedify_vocab.CryptographicKey ? key.id : key;
236
+ if (keyId == null) return null;
237
+ const resolution = await require_http.resolvePortableActorKey(keyId, options);
238
+ if (resolution.type !== "verified") return null;
239
+ if (key instanceof _fedify_vocab.CryptographicKey) {
240
+ if (key.publicKey == null || !await require_http.isSamePublicKey(key.publicKey, resolution.key.publicKey)) {
241
+ logger$1.debug("The key {keyId} does not have the same key material as the key its portable actor vouches for.", { keyId: keyId.href });
242
+ return null;
243
+ }
244
+ }
245
+ return resolution.actor;
246
+ }
247
+ //#endregion
9
248
  //#region src/sig/portable-collection.ts
10
249
  const logger = (0, _logtape_logtape.getLogger)([
11
250
  "fedify",
@@ -413,6 +652,18 @@ async function verifyPortableObject(jsonLd, options = {}) {
413
652
  };
414
653
  }
415
654
  //#endregion
655
+ Object.defineProperty(exports, "doesActorOwnKey", {
656
+ enumerable: true,
657
+ get: function() {
658
+ return doesActorOwnKey;
659
+ }
660
+ });
661
+ Object.defineProperty(exports, "getKeyOwner", {
662
+ enumerable: true,
663
+ get: function() {
664
+ return getKeyOwner;
665
+ }
666
+ });
416
667
  Object.defineProperty(exports, "verifyPortableObject", {
417
668
  enumerable: true,
418
669
  get: function() {
@@ -0,0 +1,395 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { d as isSamePublicKey, i as getRawCanonicalPortableId, s as isPortableActorDocument, t as getCanonicalPortableId, u as isPortableUri } from "./portable-key-id-Bg259c4Z.mjs";
5
+ import { l as verifyPortableObjectProofWithRoot } from "./proof-B4_0E-KU.mjs";
6
+ import { getLogger } from "@logtape/logtape";
7
+ import { CryptographicKey, Multikey, Object as Object$1 } from "@fedify/vocab";
8
+ import { FetchError, formatIri, getDocumentLoader, isGatewayUrl, parseIri, toCompatibleEf61Id } from "@fedify/vocab-runtime";
9
+ import { getPortableGatewayCandidates } from "@fedify/vocab-runtime/internal/portable-dereference";
10
+ //#region src/sig/portable-key.ts
11
+ const logger = getLogger([
12
+ "fedify",
13
+ "sig",
14
+ "key"
15
+ ]);
16
+ const SEC = "https://w3id.org/security#";
17
+ /**
18
+ * Verifies that the signed document of a portable actor vouches for
19
+ * the gateway key with the given ID.
20
+ *
21
+ * A gateway key is accepted only if:
22
+ *
23
+ * - the document is an actor whose portable ID, i.e., its `ap:` ID or
24
+ * the canonical portable ID of its compatible identifier, is the one
25
+ * the key ID is the compatible identifier of;
26
+ * - the document has a valid [FEP-8b32] Object Integrity Proof made by
27
+ * the DID of its ID, i.e., it satisfies the [FEP-ef61] proof policy;
28
+ * - the document embeds a `Multikey` with that ID in its `assertionMethod`,
29
+ * whose `controller` is the actor, as [FEP-521a] requires, and a key
30
+ * embedded in its `publicKey` with the same ID agrees with it; or, if
31
+ * no `assertionMethod` entry has that ID, the document embeds
32
+ * a `CryptographicKey` with that ID in its `publicKey`, whose `owner` is
33
+ * the actor, as some publishers, e.g., tootik, list their RSA keys only
34
+ * there;
35
+ * - no more than one entry of either property has that ID; and
36
+ * - the key ID's origin is one of the actor's `gateways`.
37
+ *
38
+ * An entry has the key's ID if its ID is the key ID, or any compatible
39
+ * identifier on the same gateway, or the `ap:` or `ap+ef61:` URI, with the
40
+ * same canonical portable ID. Some publishers, e.g., Mitra, list the keys
41
+ * of portable actors under `ap:` URIs, but sign requests with compatible key
42
+ * IDs on their own gateways. IDs are compared as they are written in
43
+ * the signed document, so an ID that URL parsing would turn into another,
44
+ * e.g., by resolving dot segments, has no match.
45
+ *
46
+ * [FEP-8b32]: https://w3id.org/fep/8b32
47
+ * [FEP-ef61]: https://w3id.org/fep/ef61
48
+ * [FEP-521a]: https://w3id.org/fep/521a
49
+ *
50
+ * @param document The raw JSON-LD document served at the key ID.
51
+ * @param actor The parsed document, which must be a portable actor.
52
+ * @param keyId The key ID, which must be a compatible key ID.
53
+ * @param options Loaders and telemetry providers.
54
+ * @returns The verification result, which is never `legacy`.
55
+ * @internal
56
+ */
57
+ function verifyPortableGatewayKeyDocument(document, actor, keyId, options = {}) {
58
+ return verifyPortableKeyDocument(document, actor, keyId, true, options);
59
+ }
60
+ /**
61
+ * Verifies that the signed document of a portable actor vouches for the key
62
+ * with the given ID, either a gateway key at a compatible key ID, or a key of
63
+ * the actor itself at an `ap:` or `ap+ef61:` key ID. See
64
+ * {@link verifyPortableGatewayKeyDocument} for the rules; a key of the actor
65
+ * itself names no gateway, so instead of the key ID's origin being one of
66
+ * the actor's `gateways`, the actor only has to have a valid gateway, as
67
+ * FEP-ef61 requires of every portable actor.
68
+ */
69
+ async function verifyPortableKeyDocument(document, actor, keyId, isGatewayKey, options) {
70
+ const reject = (reason) => {
71
+ logger.debug("Failed to verify key {keyId} of the portable actor {actorId}: {reason}", {
72
+ keyId: formatIri(keyId),
73
+ actorId: actor.id?.href,
74
+ reason
75
+ });
76
+ return {
77
+ type: "rejected",
78
+ reason
79
+ };
80
+ };
81
+ const keyBase = new URL(keyId.href);
82
+ keyBase.hash = "";
83
+ const claimedActorId = getCanonicalPortableId(keyBase);
84
+ const claimedKeyId = getCanonicalPortableId(keyId);
85
+ const actorId = actor.id == null ? null : getCanonicalPortableId(actor.id);
86
+ if (claimedActorId == null || claimedKeyId == null || actorId == null) return reject("The key ID or the actor ID is not a valid portable ID.");
87
+ if (claimedActorId !== actorId) return reject("The key ID is not a compatible identifier of the actor it dereferences to.");
88
+ let verification;
89
+ try {
90
+ verification = await verifyPortableObjectProofWithRoot(document, {
91
+ documentLoader: options.documentLoader,
92
+ contextLoader: options.contextLoader,
93
+ keyCache: options.keyCache,
94
+ tracerProvider: options.tracerProvider,
95
+ meterProvider: options.meterProvider
96
+ });
97
+ } catch (error) {
98
+ if (!(error instanceof TypeError)) throw error;
99
+ return reject(`The actor document is malformed: ${error.message}`);
100
+ }
101
+ const { result, root, expires } = verification;
102
+ if (!result.verified) return reject("The actor document does not satisfy the FEP-ef61 proof policy: " + result.reason.type);
103
+ if (root == null) return reject("The actor document could not be expanded.");
104
+ const refuse = (url) => Promise.reject(/* @__PURE__ */ new Error(`Refusing to fetch ${url}.`));
105
+ const parseEmbeddedPublicKey = async (node) => {
106
+ if (isReference(node)) return null;
107
+ let key;
108
+ try {
109
+ key = await CryptographicKey.fromJsonLd(node, {
110
+ documentLoader: refuse,
111
+ contextLoader: options.contextLoader,
112
+ tracerProvider: options.tracerProvider
113
+ });
114
+ } catch (error) {
115
+ if (!(error instanceof TypeError)) throw error;
116
+ return null;
117
+ }
118
+ return key.publicKey == null ? null : key;
119
+ };
120
+ let verifiedActor;
121
+ try {
122
+ verifiedActor = await Object$1.fromJsonLd(root, {
123
+ documentLoader: refuse,
124
+ contextLoader: options.contextLoader,
125
+ tracerProvider: options.tracerProvider
126
+ });
127
+ } catch (error) {
128
+ if (!(error instanceof TypeError)) throw error;
129
+ return reject("The verified actor document is malformed.");
130
+ }
131
+ const rootId = root["@id"];
132
+ if (!isPortableActorDocument(verifiedActor) || getCanonicalPortableId(verifiedActor.id) !== claimedActorId || typeof rootId !== "string" || getRawCanonicalPortableId(rootId) !== claimedActorId) return reject("The key ID is not a compatible identifier of the actor whose document is signed.");
133
+ actor = verifiedActor;
134
+ const findKeyNodes = (values) => findNodes(values, claimedKeyId, isGatewayKey ? keyId.origin : null);
135
+ const assertionNodes = findKeyNodes(root[`${SEC}assertionMethod`]);
136
+ const publicKeyNodes = findKeyNodes(root[`${SEC}publicKey`]);
137
+ if (assertionNodes.length > 1 || publicKeyNodes.length > 1) return reject("The actor document has more than one key with the ID.");
138
+ let publicKey;
139
+ if (assertionNodes.length > 0) {
140
+ if (isReference(assertionNodes[0])) return reject("The actor document does not embed the key in its assertionMethod.");
141
+ let multikey;
142
+ try {
143
+ multikey = await Multikey.fromJsonLd(assertionNodes[0], {
144
+ documentLoader: refuse,
145
+ contextLoader: options.contextLoader,
146
+ tracerProvider: options.tracerProvider
147
+ });
148
+ } catch (error) {
149
+ if (!(error instanceof TypeError)) throw error;
150
+ return reject("The key in the assertionMethod is malformed.");
151
+ }
152
+ if (multikey.publicKey == null) return reject("The key in the assertionMethod has no public key.");
153
+ if (multikey.controllerId == null || getCanonicalPortableId(multikey.controllerId) !== actorId) return reject("The controller of the key is not the actor.");
154
+ publicKey = multikey.publicKey;
155
+ if (publicKeyNodes.length > 0 && !isReference(publicKeyNodes[0])) {
156
+ const embedded = await parseEmbeddedPublicKey(publicKeyNodes[0]);
157
+ 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.");
158
+ }
159
+ } else {
160
+ if (publicKeyNodes.length < 1) return reject("The actor document embeds the key in neither its assertionMethod nor its publicKey.");
161
+ const embedded = await parseEmbeddedPublicKey(publicKeyNodes[0]);
162
+ if (embedded == null) return reject("The key in the publicKey is not embedded or malformed.");
163
+ if (embedded.ownerId == null || getCanonicalPortableId(embedded.ownerId) !== actorId) return reject("The owner of the key is not the actor.");
164
+ publicKey = embedded.publicKey;
165
+ }
166
+ if (isGatewayKey) {
167
+ 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.`);
168
+ } else if (!actor.gateways.some(isGatewayUrl)) return reject("The actor has no valid gateways.");
169
+ const key = new CryptographicKey({
170
+ id: keyId,
171
+ owner: actor.id,
172
+ publicKey
173
+ });
174
+ return {
175
+ type: "verified",
176
+ actor,
177
+ key,
178
+ ...expires == null ? {} : { expires }
179
+ };
180
+ }
181
+ /**
182
+ * Fetches the document at a compatible key ID, and resolves the key as
183
+ * a gateway key if the document is a portable actor.
184
+ *
185
+ * @param keyId The key ID, which must be a compatible key ID.
186
+ * @param options Loaders and telemetry providers.
187
+ * @returns `legacy` if the document cannot be fetched or is not a portable
188
+ * actor, which leaves the key to the usual resolution; otherwise,
189
+ * the result of {@link verifyPortableGatewayKeyDocument}.
190
+ * @internal
191
+ */
192
+ async function fetchPortableGatewayKey(keyId, options = {}) {
193
+ const documentLoader = options.documentLoader ?? getDocumentLoader();
194
+ const contextLoader = options.contextLoader ?? getDocumentLoader();
195
+ let remoteDocument;
196
+ try {
197
+ remoteDocument = await documentLoader(keyId.href);
198
+ } catch (error) {
199
+ logger.debug("Failed to fetch key {keyId}: {error}", {
200
+ keyId: keyId.href,
201
+ error
202
+ });
203
+ return { type: "legacy" };
204
+ }
205
+ let object;
206
+ try {
207
+ object = await Object$1.fromJsonLd(remoteDocument.document, {
208
+ documentLoader,
209
+ contextLoader,
210
+ tracerProvider: options.tracerProvider
211
+ });
212
+ } catch (error) {
213
+ if (!(error instanceof TypeError)) throw error;
214
+ return { type: "legacy" };
215
+ }
216
+ if (!isPortableActorDocument(object)) return { type: "legacy" };
217
+ return await verifyPortableGatewayKeyDocument(remoteDocument.document, object, keyId, {
218
+ ...options,
219
+ documentLoader,
220
+ contextLoader
221
+ });
222
+ }
223
+ /**
224
+ * Resolves a key of a portable actor at an `ap:` or `ap+ef61:` key ID, e.g.,
225
+ * `ap://did:key:z6Mk…/actor#main-key`. Such a key ID names no gateway, so
226
+ * the actor's document, i.e., the key ID without its fragment, is fetched
227
+ * through the gateways in its `@gateway` location hints, one after another,
228
+ * until one of them serves a document that vouches for the key. Without
229
+ * location hints, the document loader is asked for the `ap:` URI itself,
230
+ * which only a custom document loader can resolve. The hints are chosen by
231
+ * whoever made the signature, but that does not matter: only a document with
232
+ * a valid proof by the DID of the key ID vouches for the key.
233
+ *
234
+ * The key is taken as a key of the actor itself, not of a gateway, so it is
235
+ * accepted by the rules of {@link verifyPortableGatewayKeyDocument}, except
236
+ * that it is listed under the `ap:` or `ap+ef61:` URI with the same canonical
237
+ * ID as the key ID, and that the actor only has to have a valid gateway.
238
+ * The key ID must have a fragment; a key served as a standalone document is
239
+ * not supported.
240
+ *
241
+ * If no gateway serves a document that vouches for the key, the result is
242
+ * `rejected` only if every gateway served a document that definitely does not
243
+ * vouch for it. If every gateway failed to serve a document at all, the
244
+ * result is a cacheable `unavailable`, with the error of the first gateway if
245
+ * every gateway responded with the same HTTP status, so that the status
246
+ * describes the whole lookup. Otherwise, e.g., if one gateway was
247
+ * unreachable and another served an invalid document, nothing tells what the
248
+ * unreachable gateway would have served, so the result is an `unavailable`
249
+ * that is not cacheable.
250
+ *
251
+ * @param keyId The key ID, which must be an `ap:` or `ap+ef61:` URI.
252
+ * @param options Loaders and telemetry providers.
253
+ * @returns The resolution, which is never `legacy`.
254
+ * @internal
255
+ */
256
+ async function resolvePortableActorKey(keyId, options = {}) {
257
+ const documentLoader = options.documentLoader ?? getDocumentLoader();
258
+ const contextLoader = options.contextLoader ?? getDocumentLoader();
259
+ let formattedKeyId = keyId.href;
260
+ const reject = (reason) => {
261
+ logger.debug("Failed to verify key {keyId} of a portable actor: {reason}", {
262
+ keyId: formattedKeyId,
263
+ reason
264
+ });
265
+ return {
266
+ type: "rejected",
267
+ reason
268
+ };
269
+ };
270
+ if (!isPortableUri(keyId) || getCanonicalPortableId(keyId) == null) return reject("The key ID is not a valid ap: or ap+ef61: URI.");
271
+ formattedKeyId = formatIri(keyId);
272
+ if (keyId.hash === "" || keyId.hash === "#") return reject("The key ID has no fragment; only keys embedded in actor documents are supported.");
273
+ const actorUrl = new URL(keyId.href);
274
+ actorUrl.hash = "";
275
+ const requestUrls = [];
276
+ for (const gateway of getPortableGatewayCandidates(actorUrl)) try {
277
+ requestUrls.push(toCompatibleEf61Id(actorUrl, gateway).href);
278
+ } catch (error) {
279
+ if (!(error instanceof TypeError)) throw error;
280
+ return reject(`The key ID cannot be fetched from gateways: ${error}`);
281
+ }
282
+ if (requestUrls.length < 1) requestUrls.push(formatIri(actorUrl));
283
+ const attempts = [];
284
+ for (const url of requestUrls) {
285
+ let remoteDocument;
286
+ try {
287
+ remoteDocument = await documentLoader(url);
288
+ } catch (error) {
289
+ logger.debug("Failed to fetch the actor of key {keyId} from {url}: {error}", {
290
+ keyId: formattedKeyId,
291
+ url,
292
+ error
293
+ });
294
+ attempts.push({
295
+ type: "retrievalFailed",
296
+ error
297
+ });
298
+ continue;
299
+ }
300
+ let resolution;
301
+ try {
302
+ const object = await Object$1.fromJsonLd(remoteDocument.document, {
303
+ documentLoader,
304
+ contextLoader,
305
+ tracerProvider: options.tracerProvider
306
+ });
307
+ resolution = isPortableActorDocument(object) ? await verifyPortableKeyDocument(remoteDocument.document, object, keyId, false, {
308
+ ...options,
309
+ documentLoader,
310
+ contextLoader
311
+ }) : {
312
+ type: "rejected",
313
+ reason: "The document is not a portable actor."
314
+ };
315
+ } catch (error) {
316
+ if (error instanceof TypeError) resolution = {
317
+ type: "rejected",
318
+ reason: `The document is malformed: ${error.message}`
319
+ };
320
+ else {
321
+ logger.debug("Failed to process the actor of key {keyId} served from {url}: {error}", {
322
+ keyId: formattedKeyId,
323
+ url,
324
+ error
325
+ });
326
+ attempts.push({
327
+ type: "indeterminate",
328
+ error
329
+ });
330
+ continue;
331
+ }
332
+ }
333
+ if (resolution.type === "verified") return resolution;
334
+ const reason = resolution.type === "rejected" ? resolution.reason : "The document is not a portable actor.";
335
+ logger.debug("The actor document served from {url} does not vouch for key {keyId}: {reason}", {
336
+ keyId: formattedKeyId,
337
+ url,
338
+ reason
339
+ });
340
+ attempts.push({
341
+ type: "rejected",
342
+ reason
343
+ });
344
+ }
345
+ const rejections = attempts.flatMap((a) => a.type === "rejected" ? [a.reason] : []);
346
+ if (rejections.length === attempts.length) return reject(rejections.join(" "));
347
+ const errors = attempts.flatMap((a) => a.type === "rejected" ? [] : [a.error]);
348
+ const aggregate = errors.length === 1 ? errors[0] : new AggregateError(errors, `Failed to fetch the actor of key ${formattedKeyId} from any of its gateways: ${requestUrls.join(", ")}`);
349
+ if (attempts.every((a) => a.type === "retrievalFailed")) {
350
+ const statuses = errors.map((error) => error instanceof FetchError ? error.response?.status : void 0);
351
+ return {
352
+ type: "unavailable",
353
+ error: statuses[0] != null && statuses.every((status) => status === statuses[0]) ? errors[0] : aggregate,
354
+ cacheable: true
355
+ };
356
+ }
357
+ return {
358
+ type: "unavailable",
359
+ error: new Error(`Could not determine whether the actor of key ${formattedKeyId} vouches for it.`, { cause: aggregate }),
360
+ cacheable: false
361
+ };
362
+ }
363
+ /**
364
+ * Finds the nodes that stand for the key with the given canonical portable
365
+ * ID. A node whose ID is an `ap:` or `ap+ef61:` URI stands for the key
366
+ * itself, e.g., Mitra lists the keys of portable actors that way while
367
+ * signing requests with compatible key IDs. A node whose ID is a compatible
368
+ * identifier stands for a gateway's key, so it matches only a compatible key
369
+ * ID on the same gateway; a key ID that names no gateway never selects
370
+ * a gateway's key, even with the same fragment.
371
+ * @param values The expanded values of a key property.
372
+ * @param canonicalKeyId The canonical portable ID of the key ID.
373
+ * @param gateway The origin of the gateway whose key the key ID names, or
374
+ * `null` if the key ID names no gateway.
375
+ */
376
+ function findNodes(values, canonicalKeyId, gateway) {
377
+ if (!Array.isArray(values)) return [];
378
+ const nodes = [];
379
+ for (const value of values) {
380
+ if (value == null || typeof value !== "object" || Array.isArray(value)) continue;
381
+ const node = value;
382
+ const nodeId = node["@id"];
383
+ if (typeof nodeId !== "string") continue;
384
+ if (getRawCanonicalPortableId(nodeId) !== canonicalKeyId) continue;
385
+ const parsed = parseIri(nodeId);
386
+ if (isPortableUri(parsed) || parsed.origin === gateway) nodes.push(node);
387
+ }
388
+ return nodes;
389
+ }
390
+ /** A node with nothing but an `@id` is a reference, not an embedded key. */
391
+ function isReference(node) {
392
+ return Object.keys(node).every((key) => key === "@id");
393
+ }
394
+ //#endregion
395
+ export { resolvePortableActorKey as n, verifyPortableGatewayKeyDocument as r, fetchPortableGatewayKey as t };