@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.
- package/dist/{builder-BGlpUocK.mjs → builder-Ld4Y9ymR.mjs} +34 -3
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-B6Aj2kGY.mjs → compound-proof-CTx_lewQ.mjs} +191 -13
- package/dist/{deno-Dn-py9Zy.mjs → deno-8t8k_6Yv.mjs} +1 -1
- package/dist/{docloader-BUq5bYJr.mjs → docloader-DJKcsAiF.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +3 -3
- package/dist/federation/handler-compound-proof.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +7 -7
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.js +1 -1
- package/dist/federation/outgoing-proof.test.mjs +254 -5
- package/dist/federation/portable-gateway-keys.test.mjs +72 -11
- package/dist/federation/portable-inbox.test.mjs +116 -4
- package/dist/federation/portable.test.mjs +182 -4
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +89 -1
- package/dist/{federation-BLGx8JUH.cjs → federation-aSHO04ob.cjs} +360 -65
- package/dist/{http-B0VRNipE.mjs → http-B2ftY0Ha.mjs} +4 -4
- package/dist/{http-B5q1CHtL.cjs → http-DSaSAN7X.cjs} +1609 -1457
- package/dist/{http-4PCCvhsU.js → http-DyT7DP1Y.js} +1518 -1390
- package/dist/{key-BJrp524f.mjs → key-Dwm4VswT.mjs} +39 -99
- package/dist/{kv-cache-Dhhhrryv.cjs → kv-cache-BSJAlP4C.cjs} +1 -1
- package/dist/{kv-cache-C4C3LyYW.mjs → kv-cache-D0yYukqk.mjs} +1 -1
- package/dist/{kv-cache-1KUmAPH7.js → kv-cache-DmroAz3M.js} +1 -1
- package/dist/{ld-BmFz5NLA.mjs → ld-BYZOoDzZ.mjs} +3 -3
- package/dist/{metrics-Beod6tzO.mjs → metrics-B7YwKTFr.mjs} +1 -1
- package/dist/{middleware-B7CNsyqt.mjs → middleware-BNeqKSsL.mjs} +1 -1
- package/dist/{middleware-D_9pNYe9.js → middleware-Blq0882d.js} +368 -73
- package/dist/{middleware-BKeQYPm0.mjs → middleware-E_0IH72m.mjs} +166 -71
- package/dist/{mod-HDSvS-ww.d.cts → mod-C8LieBzF.d.cts} +16 -3
- package/dist/{mod-C9WSpZWm.d.ts → mod-Coacostr.d.ts} +16 -3
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +1 -1
- package/dist/mod.d.ts +1 -1
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-CsKVgMzk.mjs → owner-Cgx2Dc7I.mjs} +5 -4
- package/dist/{owner-_pbbgLJK.js → owner-DAGSC5gX.js} +2 -2
- package/dist/{owner-BQFZtFV4.cjs → owner-szIZl4qI.cjs} +2 -2
- package/dist/{portable-key-B4R-CiIV.mjs → portable-key-Cn1WJSN1.mjs} +60 -41
- package/dist/portable-key-id-Bva5iT3W.mjs +136 -0
- package/dist/{proof-ozG4HfcQ.mjs → proof-DVXC6nYz.mjs} +59 -18
- package/dist/{send-ClPt8nYu.mjs → send-D-hOvYKy.mjs} +3 -3
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +160 -3
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +3 -3
- package/dist/sig/mod.d.cts +1 -1
- package/dist/sig/mod.d.ts +1 -1
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +69 -5
- package/dist/sig/portable-key.test.mjs +176 -10
- package/dist/sig/proof.test.mjs +107 -4
- package/dist/{sig-DhdomAN0.js → sig-B5Ell7pO.js} +19 -3
- package/dist/{sig-k3C8sF7g.cjs → sig-CXOKN4Br.cjs} +19 -3
- package/dist/{temporal-Ba0EAZAI.mjs → temporal-CqpKGEPk.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- 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 {
|
|
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-
|
|
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 {
|
|
5
|
-
import {
|
|
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
|
|
23
|
-
* the compatible identifier
|
|
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
|
|
28
|
-
* embedded in its `publicKey` with the same ID
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
175
|
-
if (!Array.isArray(values)) return
|
|
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
|
-
|
|
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
|
|
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-
|
|
5
|
-
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-
|
|
6
|
-
import { r as
|
|
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-
|
|
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,
|
|
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" && !
|
|
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" || !
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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 {
|
|
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-
|
|
5
|
-
import { n as getDurationMs, r as getFederationMetrics } from "./metrics-
|
|
6
|
-
import { n as doubleKnock } from "./http-
|
|
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-
|
|
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-
|
|
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";
|