@fedify/fedify 2.1.0-dev.565 → 2.1.0-dev.599
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/accept-D7sAxyNa.js +143 -0
- package/dist/{assert_rejects-Ce45JcFg.js → assert_rejects-0h7I2Esa.js} +1 -1
- package/dist/{builder-Deoi2N2z.js → builder-rlJT9XsH.js} +3 -3
- package/dist/compat/mod.d.cts +3 -3
- package/dist/compat/mod.d.ts +3 -3
- package/dist/compat/transformers.test.js +17 -16
- package/dist/{context-DL0cPpPV.d.cts → context-BcqA-0BL.d.cts} +52 -2
- package/dist/{context--RwChtri.d.ts → context-DyJjQQ_H.d.ts} +52 -2
- package/dist/{deno-CEdy89j9.js → deno-BYv1FXyT.js} +1 -2
- package/dist/{docloader-CL1QPJzN.js → docloader-D3dGL8MN.js} +2 -2
- package/dist/federation/builder.test.js +7 -7
- package/dist/federation/collection.test.js +5 -5
- package/dist/federation/handler.test.js +806 -26
- package/dist/federation/idempotency.test.js +22 -21
- package/dist/federation/inbox.test.js +3 -3
- package/dist/federation/keycache.test.js +1 -1
- package/dist/federation/kv.test.js +4 -4
- package/dist/federation/middleware.test.js +22 -21
- package/dist/federation/mod.cjs +4 -4
- package/dist/federation/mod.d.cts +4 -4
- package/dist/federation/mod.d.ts +4 -4
- package/dist/federation/mod.js +4 -4
- package/dist/federation/mq.test.js +4 -4
- package/dist/federation/negotiation.test.js +5 -5
- package/dist/federation/retry.test.js +2 -2
- package/dist/federation/router.test.js +4 -4
- package/dist/federation/send.test.js +11 -10
- package/dist/federation/webfinger.test.js +22 -21
- package/dist/{http-DsqqmkXi.d.cts → http-BudnHZE2.d.cts} +229 -1
- package/dist/{http-iDlaLy8a.cjs → http-Cjdbgipx.cjs} +307 -50
- package/dist/{http-BbfOqHGG.d.ts → http-Dax_FIBo.d.ts} +229 -1
- package/dist/{http-Dm9n1mRe.js → http-SDJbghtm.js} +144 -49
- package/dist/{http-VpqmUjje.js → http-WrV4DdQ1.js} +278 -51
- package/dist/{inbox-CMtnW0RE.js → inbox-gPJ0RaKj.js} +1 -1
- package/dist/{key-B0yADkL8.js → key-BSOrewQw.js} +1 -1
- package/dist/{kv-cache-BSATpUtX.js → kv-cache-D0a-g8yG.js} +1 -1
- package/dist/{kv-cache-551Om14-.cjs → kv-cache-sn8V-LU_.cjs} +1 -1
- package/dist/{ld-BBmbv1nb.js → ld-8UNDFIO0.js} +3 -3
- package/dist/middleware-74Kx7iWO.cjs +12 -0
- package/dist/{middleware-DpdPMZII.js → middleware-Brge9sYu.js} +4 -4
- package/dist/{middleware-Cldp2YSv.js → middleware-CWItYNUL.js} +113 -34
- package/dist/{middleware-Cx0tTbX1.js → middleware-C_89nqvv.js} +95 -18
- package/dist/{middleware-D11GYoP-.cjs → middleware-PGDxr3nC.cjs} +94 -17
- package/dist/middleware-nfE7By0g.js +27 -0
- package/dist/{mod-DE8MYisy.d.cts → mod-B7QkWzrL.d.cts} +1 -1
- package/dist/{mod-DKG0ovjR.d.cts → mod-Bx9jcLB8.d.cts} +1 -1
- package/dist/{mod-CFBU2OT3.d.cts → mod-Coe7KEgX.d.cts} +1 -1
- package/dist/{mod-BugwI0JN.d.ts → mod-Cs2dYEwI.d.ts} +1 -1
- package/dist/{mod-DcfFNgYf.d.ts → mod-D6MdymW7.d.ts} +1 -1
- package/dist/{mod-CvxylbuV.d.ts → mod-D6dOd--H.d.ts} +1 -1
- package/dist/{mod-Z7lIaCfo.d.ts → mod-SMHOMNpZ.d.ts} +1 -1
- package/dist/{mod-Dp0kK0hO.d.cts → mod-em2Il1eD.d.cts} +1 -1
- package/dist/mod.cjs +12 -4
- package/dist/mod.d.cts +8 -8
- package/dist/mod.d.ts +8 -8
- package/dist/mod.js +9 -5
- package/dist/nodeinfo/client.test.js +4 -4
- package/dist/nodeinfo/handler.test.js +22 -21
- package/dist/nodeinfo/types.test.js +4 -4
- package/dist/otel/exporter.test.js +4 -4
- package/dist/{owner-C1ZyG4NL.js → owner-D1i3Gz1q.js} +1 -1
- package/dist/{proof-wclcUq0C.js → proof-Blm7rPHe.js} +2 -2
- package/dist/{proof-CgK60TcQ.cjs → proof-DFqGzNZi.cjs} +3 -3
- package/dist/{proof-DnRq8s8f.js → proof-DgU0YpXY.js} +2 -2
- package/dist/{send-DNJyYRVU.js → send-Ban_thmx.js} +2 -2
- package/dist/sig/accept.test.d.ts +3 -0
- package/dist/sig/accept.test.js +451 -0
- package/dist/sig/http.test.js +452 -27
- package/dist/sig/key.test.js +7 -7
- package/dist/sig/ld.test.js +6 -6
- package/dist/sig/mod.cjs +6 -2
- package/dist/sig/mod.d.cts +3 -3
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.js +8 -8
- package/dist/sig/proof.test.js +8 -8
- package/dist/testing/mod.js +1 -1
- package/dist/utils/docloader.test.js +10 -9
- package/dist/utils/kv-cache.test.js +1 -1
- package/dist/utils/mod.cjs +2 -2
- package/dist/utils/mod.d.cts +2 -2
- package/dist/utils/mod.d.ts +2 -2
- package/dist/utils/mod.js +2 -2
- package/package.json +6 -7
- package/dist/middleware-BDr0P6dx.cjs +0 -12
- package/dist/middleware-BZ8WpBo6.js +0 -26
- /package/dist/{assert_not_equals-C80BG-_5.js → assert_not_equals-f3m3epl3.js} +0 -0
- /package/dist/{assert_throws-BNXdRGWP.js → assert_throws-rjdMBf31.js} +0 -0
- /package/dist/{collection-CcnIw1qY.js → collection-CSzG2j1P.js} +0 -0
- /package/dist/{context-pa9aIrwp.js → context-Aqenou7c.js} +0 -0
- /package/dist/{keycache-C7k8s1Bk.js → keycache-CpGWAUbj.js} +0 -0
- /package/dist/{keys-ZbcByPg9.js → keys-BFve7QQv.js} +0 -0
- /package/dist/{kv-cache-El7We5sy.js → kv-cache-Bw2F2ABq.js} +0 -0
- /package/dist/{negotiation-5NPJL6zp.js → negotiation-BlAuS_nr.js} +0 -0
- /package/dist/{retry-D4GJ670a.js → retry-mqLf4b-R.js} +0 -0
- /package/dist/{std__assert-DWivtrGR.js → std__assert-X-_kMxKM.js} +0 -0
|
@@ -6,14 +6,14 @@ import { getLogger } from "@logtape/logtape";
|
|
|
6
6
|
import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
7
7
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
8
8
|
import { encodeHex } from "byte-encodings/hex";
|
|
9
|
+
import { Item, decodeDict, encodeDict, encodeItem } from "structured-field-values";
|
|
9
10
|
import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
|
|
10
11
|
import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
|
|
11
|
-
import { Item, decodeDict, encodeItem } from "structured-field-values";
|
|
12
12
|
import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
13
13
|
|
|
14
14
|
//#region deno.json
|
|
15
15
|
var name = "@fedify/fedify";
|
|
16
|
-
var version = "2.1.0-dev.
|
|
16
|
+
var version = "2.1.0-dev.599+1ff26884";
|
|
17
17
|
var license = "MIT";
|
|
18
18
|
var exports = {
|
|
19
19
|
".": "./src/mod.ts",
|
|
@@ -35,7 +35,6 @@ var imports = {
|
|
|
35
35
|
"fetch-mock": "npm:fetch-mock@^12.5.2",
|
|
36
36
|
"json-canon": "npm:json-canon@^1.0.1",
|
|
37
37
|
"jsonld": "npm:jsonld@^9.0.0",
|
|
38
|
-
"multicodec": "npm:multicodec@^3.2.1",
|
|
39
38
|
"pkijs": "npm:pkijs@^3.3.3",
|
|
40
39
|
"structured-field-values": "npm:structured-field-values@^2.0.4",
|
|
41
40
|
"uri-template-router": "npm:uri-template-router@^1.0.0",
|
|
@@ -126,6 +125,140 @@ var deno_default = {
|
|
|
126
125
|
tasks
|
|
127
126
|
};
|
|
128
127
|
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/sig/accept.ts
|
|
130
|
+
/**
|
|
131
|
+
* Parses an `Accept-Signature` header value (RFC 9421 §5.1) into an
|
|
132
|
+
* array of {@link AcceptSignatureMember} objects.
|
|
133
|
+
*
|
|
134
|
+
* The `Accept-Signature` field is a Dictionary Structured Field
|
|
135
|
+
* (RFC 8941 §3.2). Each dictionary member describes a single
|
|
136
|
+
* requested message signature.
|
|
137
|
+
*
|
|
138
|
+
* On parse failure (malformed or empty header), returns an empty array.
|
|
139
|
+
*
|
|
140
|
+
* @param header The raw `Accept-Signature` header value string.
|
|
141
|
+
* @returns An array of parsed members. Empty if the header is
|
|
142
|
+
* malformed or empty.
|
|
143
|
+
* @since 2.1.0
|
|
144
|
+
*/
|
|
145
|
+
function parseAcceptSignature(header) {
|
|
146
|
+
try {
|
|
147
|
+
return parseEachSignature(decodeDict(header));
|
|
148
|
+
} catch {
|
|
149
|
+
getLogger([
|
|
150
|
+
"fedify",
|
|
151
|
+
"sig",
|
|
152
|
+
"http"
|
|
153
|
+
]).warn("Failed to parse Accept-Signature header: {header}", { header });
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const compactObject = (obj) => Object.fromEntries(Object.entries(obj).filter(([_, v]) => v !== void 0));
|
|
158
|
+
const parseEachSignature = (dict) => Object.entries(dict).filter(([_, item]) => Array.isArray(item.value)).map(([label, item]) => ({
|
|
159
|
+
label,
|
|
160
|
+
components: item.value.filter((subitem) => typeof subitem.value === "string").map((subitem) => ({
|
|
161
|
+
value: subitem.value,
|
|
162
|
+
params: subitem.params ?? {}
|
|
163
|
+
})),
|
|
164
|
+
parameters: compactParams(item)
|
|
165
|
+
}));
|
|
166
|
+
const compactParams = (item) => {
|
|
167
|
+
const { keyid, alg, created, expires, nonce, tag } = item.params ?? {};
|
|
168
|
+
return compactObject({
|
|
169
|
+
keyid: stringOrUndefined(keyid),
|
|
170
|
+
alg: stringOrUndefined(alg),
|
|
171
|
+
created: trueOrUndefined(created),
|
|
172
|
+
expires: trueOrUndefined(expires),
|
|
173
|
+
nonce: stringOrUndefined(nonce),
|
|
174
|
+
tag: stringOrUndefined(tag)
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
const stringOrUndefined = (v) => typeof v === "string" ? v : void 0;
|
|
178
|
+
const trueOrUndefined = (v) => v === true ? true : void 0;
|
|
179
|
+
/**
|
|
180
|
+
* Serializes an array of {@link AcceptSignatureMember} objects into an
|
|
181
|
+
* `Accept-Signature` header value string (RFC 9421 §5.1).
|
|
182
|
+
*
|
|
183
|
+
* The output is a Dictionary Structured Field (RFC 8941 §3.2).
|
|
184
|
+
*
|
|
185
|
+
* @param members The members to serialize.
|
|
186
|
+
* @returns The serialized header value string.
|
|
187
|
+
* @since 2.1.0
|
|
188
|
+
*/
|
|
189
|
+
function formatAcceptSignature(members) {
|
|
190
|
+
const items = members.map((member) => [member.label, new Item(compToItems(member), compactParameters(member))]);
|
|
191
|
+
return encodeDict(Object.fromEntries(items));
|
|
192
|
+
}
|
|
193
|
+
const compToItems = (member) => member.components.map((c) => new Item(c.value, c.params));
|
|
194
|
+
const compactParameters = (member) => {
|
|
195
|
+
const { keyid, alg, created, expires, nonce, tag } = member.parameters;
|
|
196
|
+
return compactObject({
|
|
197
|
+
keyid,
|
|
198
|
+
alg,
|
|
199
|
+
created,
|
|
200
|
+
expires,
|
|
201
|
+
nonce,
|
|
202
|
+
tag
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* Filters out {@link AcceptSignatureMember} entries whose covered
|
|
207
|
+
* components include response-only identifiers (`@status`) that are
|
|
208
|
+
* not applicable to request-target messages, as required by
|
|
209
|
+
* [RFC 9421 §5](https://www.rfc-editor.org/rfc/rfc9421#section-5).
|
|
210
|
+
*
|
|
211
|
+
* A warning is logged for each discarded entry.
|
|
212
|
+
*
|
|
213
|
+
* @param members The parsed `Accept-Signature` entries to validate.
|
|
214
|
+
* @returns Only entries that are valid for request-target messages.
|
|
215
|
+
* @since 2.1.0
|
|
216
|
+
*/
|
|
217
|
+
function validateAcceptSignature(members) {
|
|
218
|
+
const logger = getLogger([
|
|
219
|
+
"fedify",
|
|
220
|
+
"sig",
|
|
221
|
+
"http"
|
|
222
|
+
]);
|
|
223
|
+
return members.filter((member) => {
|
|
224
|
+
if (member.components.every((c) => c.value !== "@status")) return true;
|
|
225
|
+
logLabel(logger, member.label);
|
|
226
|
+
return false;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const logLabel = (logger, label) => logger.warn("Discarding Accept-Signature member {label}: covered components include response-only identifier @status.", { label });
|
|
230
|
+
/**
|
|
231
|
+
* Attempts to translate an {@link AcceptSignatureMember} challenge into
|
|
232
|
+
* RFC 9421 signing options that the local signer can fulfill.
|
|
233
|
+
*
|
|
234
|
+
* Returns `null` if the challenge cannot be fulfilled—for example, if
|
|
235
|
+
* the requested `alg` or `keyid` is incompatible with the local key.
|
|
236
|
+
*
|
|
237
|
+
* Safety constraints:
|
|
238
|
+
* - `alg`: only honored if it matches `localAlg`.
|
|
239
|
+
* - `keyid`: only honored if it matches `localKeyId`.
|
|
240
|
+
* - `components`: passed through exactly as requested, per RFC 9421 §5.2.
|
|
241
|
+
* - `nonce`, `tag`, and `expires` are passed through directly.
|
|
242
|
+
*
|
|
243
|
+
* @param entry The challenge entry from the `Accept-Signature` header.
|
|
244
|
+
* @param localKeyId The local key identifier (e.g., the actor key URL).
|
|
245
|
+
* @param localAlg The algorithm of the local private key
|
|
246
|
+
* (e.g., `"rsa-v1_5-sha256"`).
|
|
247
|
+
* @returns Signing options if the challenge can be fulfilled, or `null`.
|
|
248
|
+
* @since 2.1.0
|
|
249
|
+
*/
|
|
250
|
+
function fulfillAcceptSignature(entry, localKeyId, localAlg) {
|
|
251
|
+
if (entry.parameters.alg != null && entry.parameters.alg !== localAlg) return null;
|
|
252
|
+
if (entry.parameters.keyid != null && entry.parameters.keyid !== localKeyId) return null;
|
|
253
|
+
return {
|
|
254
|
+
label: entry.label,
|
|
255
|
+
components: entry.components,
|
|
256
|
+
nonce: entry.parameters.nonce,
|
|
257
|
+
tag: entry.parameters.tag,
|
|
258
|
+
expires: entry.parameters.expires
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
129
262
|
//#endregion
|
|
130
263
|
//#region src/sig/key.ts
|
|
131
264
|
/**
|
|
@@ -493,7 +626,7 @@ async function signRequest(request, privateKey, keyId, options = {}) {
|
|
|
493
626
|
try {
|
|
494
627
|
const spec = options.spec ?? "draft-cavage-http-signatures-12";
|
|
495
628
|
let signed;
|
|
496
|
-
if (spec === "rfc9421") signed = await signRequestRfc9421(request, privateKey, keyId, span, options.currentTime, options.body);
|
|
629
|
+
if (spec === "rfc9421") signed = await signRequestRfc9421(request, privateKey, keyId, span, options.currentTime, options.body, options.rfc9421);
|
|
497
630
|
else signed = await signRequestDraft(request, privateKey, keyId, span, options.currentTime, options.body);
|
|
498
631
|
if (span.isRecording()) {
|
|
499
632
|
span.setAttribute(ATTR_HTTP_REQUEST_METHOD, signed.method);
|
|
@@ -541,7 +674,19 @@ async function signRequestDraft(request, privateKey, keyId, span, currentTime, b
|
|
|
541
674
|
});
|
|
542
675
|
}
|
|
543
676
|
function formatRfc9421SignatureParameters(params) {
|
|
544
|
-
return
|
|
677
|
+
return Array.from(iterRfc9421(params)).join(";");
|
|
678
|
+
}
|
|
679
|
+
function* iterRfc9421(params) {
|
|
680
|
+
yield `alg="${params.algorithm}"`;
|
|
681
|
+
yield `keyid="${params.keyId.href}"`;
|
|
682
|
+
yield `created=${params.created}`;
|
|
683
|
+
if (params.expires != null) yield `expires=${params.expires}`;
|
|
684
|
+
if (params.nonce != null) yield `nonce="${escapeSfString(params.nonce)}"`;
|
|
685
|
+
if (params.tag != null) yield `tag="${escapeSfString(params.tag)}"`;
|
|
686
|
+
}
|
|
687
|
+
const escapeSfString = (value) => value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
688
|
+
function formatComponentId(component) {
|
|
689
|
+
return encodeItem(new Item(component.value, component.params));
|
|
545
690
|
}
|
|
546
691
|
/**
|
|
547
692
|
* Creates a signature base for a request according to RFC 9421.
|
|
@@ -552,30 +697,31 @@ function formatRfc9421SignatureParameters(params) {
|
|
|
552
697
|
*/
|
|
553
698
|
function createRfc9421SignatureBase(request, components, parameters) {
|
|
554
699
|
const url = new URL(request.url);
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
else if (component === "@query") value = url.search.startsWith("?") ? url.search.slice(1) : url.search;
|
|
565
|
-
else if (component === "@query-param") throw new Error("@query-param requires a parameter name");
|
|
566
|
-
else if (component === "@status") throw new Error("@status is only valid for responses");
|
|
567
|
-
else if (component.startsWith("@")) throw new Error(`Unsupported derived component: ${component}`);
|
|
568
|
-
else {
|
|
569
|
-
const header = request.headers.get(component);
|
|
570
|
-
if (header == null) throw new Error(`Missing header: ${component}`);
|
|
571
|
-
value = header;
|
|
572
|
-
}
|
|
573
|
-
baseComponents.push(`"${component}": ${value}`);
|
|
574
|
-
}
|
|
575
|
-
const sigComponents = components.map((c) => `"${c}"`).join(" ");
|
|
576
|
-
baseComponents.push(`"@signature-params": (${sigComponents});${parameters}`);
|
|
577
|
-
return baseComponents.join("\n");
|
|
700
|
+
return components.map((component) => {
|
|
701
|
+
const id = formatComponentId(component);
|
|
702
|
+
const derived = derivedComponents[component.value]?.(request, url);
|
|
703
|
+
if (derived != null) return `${id}: ${derived}`;
|
|
704
|
+
if (component.value.startsWith("@")) throw new Error(`Unsupported derived component: ${component.value}`);
|
|
705
|
+
const header = request.headers.get(component.value);
|
|
706
|
+
if (header == null) throw new Error(`Missing header: ${component.value}`);
|
|
707
|
+
return `${id}: ${header}`;
|
|
708
|
+
}).concat([`"@signature-params": (${components.map((c) => formatComponentId(c)).join(" ")});${parameters}`]).join("\n");
|
|
578
709
|
}
|
|
710
|
+
const derivedComponents = {
|
|
711
|
+
"@method": (request) => request.method.toUpperCase(),
|
|
712
|
+
"@target-uri": (_, url) => url.href,
|
|
713
|
+
"@authority": (_, url) => url.host,
|
|
714
|
+
"@scheme": (_, url) => url.protocol.slice(0, -1),
|
|
715
|
+
"@request-target": (request, url) => `${request.method.toLowerCase()} ${url.pathname}${url.search}`,
|
|
716
|
+
"@path": (_, url) => url.pathname,
|
|
717
|
+
"@query": (_, { search }) => search.startsWith("?") ? search.slice(1) : search,
|
|
718
|
+
"@query-param": () => {
|
|
719
|
+
throw new Error("@query-param requires a parameter name");
|
|
720
|
+
},
|
|
721
|
+
"@status": () => {
|
|
722
|
+
throw new Error("@status is only valid for responses");
|
|
723
|
+
}
|
|
724
|
+
};
|
|
579
725
|
/**
|
|
580
726
|
* Formats a signature using rfc9421 format.
|
|
581
727
|
* @param signature The raw signature bytes.
|
|
@@ -583,9 +729,9 @@ function createRfc9421SignatureBase(request, components, parameters) {
|
|
|
583
729
|
* @param parameters The signature parameters.
|
|
584
730
|
* @returns The formatted signature string.
|
|
585
731
|
*/
|
|
586
|
-
function formatRfc9421Signature(signature, components, parameters) {
|
|
587
|
-
const signatureInputValue =
|
|
588
|
-
const signatureValue =
|
|
732
|
+
function formatRfc9421Signature(signature, components, parameters, label = "sig1") {
|
|
733
|
+
const signatureInputValue = `${label}=(${components.map((c) => formatComponentId(c)).join(" ")});${parameters}`;
|
|
734
|
+
const signatureValue = `${label}=:${encodeBase64(signature)}:`;
|
|
589
735
|
return [signatureInputValue, signatureValue];
|
|
590
736
|
}
|
|
591
737
|
/**
|
|
@@ -611,12 +757,17 @@ function parseRfc9421SignatureInput(signatureInput) {
|
|
|
611
757
|
const result = {};
|
|
612
758
|
for (const [label, item] of Object.entries(dict)) {
|
|
613
759
|
if (!Array.isArray(item.value) || typeof item.params.keyid !== "string" || typeof item.params.created !== "number") continue;
|
|
614
|
-
const components = item.value.
|
|
760
|
+
const components = item.value.filter((subitem) => typeof subitem.value === "string").map((subitem) => ({
|
|
761
|
+
value: subitem.value,
|
|
762
|
+
params: subitem.params ?? {}
|
|
763
|
+
}));
|
|
615
764
|
const params = encodeItem(new Item(0, item.params));
|
|
616
765
|
result[label] = {
|
|
617
766
|
keyId: item.params.keyid,
|
|
618
767
|
alg: item.params.alg,
|
|
619
768
|
created: item.params.created,
|
|
769
|
+
nonce: typeof item.params.nonce === "string" ? item.params.nonce : void 0,
|
|
770
|
+
tag: typeof item.params.tag === "string" ? item.params.tag : void 0,
|
|
620
771
|
components,
|
|
621
772
|
parameters: params.slice(params.indexOf(";") + 1)
|
|
622
773
|
};
|
|
@@ -647,7 +798,7 @@ function parseRfc9421Signature(signature) {
|
|
|
647
798
|
for (const [key, value] of Object.entries(dict)) if (value.value instanceof Uint8Array) result[key] = value.value;
|
|
648
799
|
return result;
|
|
649
800
|
}
|
|
650
|
-
async function signRequestRfc9421(request, privateKey, keyId, span, currentTime, bodyBuffer) {
|
|
801
|
+
async function signRequestRfc9421(request, privateKey, keyId, span, currentTime, bodyBuffer, rfc9421Options) {
|
|
651
802
|
if (privateKey.algorithm.name !== "RSASSA-PKCS1-v1_5") throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
|
|
652
803
|
const url = new URL(request.url);
|
|
653
804
|
const body = bodyBuffer !== void 0 ? bodyBuffer : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
|
|
@@ -661,18 +812,40 @@ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime,
|
|
|
661
812
|
currentTime ??= Temporal.Now.instant();
|
|
662
813
|
const created = currentTime.epochMilliseconds / 1e3 | 0;
|
|
663
814
|
if (!headers.has("Date")) headers.set("Date", new Date(currentTime.toString()).toUTCString());
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
815
|
+
const label = rfc9421Options?.label ?? "sig1";
|
|
816
|
+
const components = [...rfc9421Options?.components ?? [
|
|
817
|
+
{
|
|
818
|
+
value: "@method",
|
|
819
|
+
params: {}
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
value: "@target-uri",
|
|
823
|
+
params: {}
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
value: "@authority",
|
|
827
|
+
params: {}
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
value: "host",
|
|
831
|
+
params: {}
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
value: "date",
|
|
835
|
+
params: {}
|
|
836
|
+
}
|
|
837
|
+
], ...body != null ? [{
|
|
838
|
+
value: "content-digest",
|
|
839
|
+
params: {}
|
|
840
|
+
}] : []];
|
|
841
|
+
const expires = rfc9421Options?.expires === true ? (currentTime.epochMilliseconds / 1e3 | 0) + 3600 : void 0;
|
|
672
842
|
const signatureParams = formatRfc9421SignatureParameters({
|
|
673
843
|
algorithm: "rsa-v1_5-sha256",
|
|
674
844
|
keyId,
|
|
675
|
-
created
|
|
845
|
+
created,
|
|
846
|
+
expires,
|
|
847
|
+
nonce: rfc9421Options?.nonce,
|
|
848
|
+
tag: rfc9421Options?.tag
|
|
676
849
|
});
|
|
677
850
|
let signatureBase;
|
|
678
851
|
try {
|
|
@@ -684,9 +857,11 @@ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime,
|
|
|
684
857
|
throw new TypeError(`Failed to create signature base: ${String(error)}; it is probably a bug in the implementation. Please report it at Fedify's issue tracker.`);
|
|
685
858
|
}
|
|
686
859
|
const signatureBytes = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", privateKey, new TextEncoder().encode(signatureBase));
|
|
687
|
-
const [signatureInput, signature] = formatRfc9421Signature(signatureBytes, components, signatureParams);
|
|
688
|
-
headers.
|
|
689
|
-
headers.set("Signature",
|
|
860
|
+
const [signatureInput, signature] = formatRfc9421Signature(signatureBytes, components, signatureParams, label);
|
|
861
|
+
const existingInput = headers.get("Signature-Input");
|
|
862
|
+
headers.set("Signature-Input", existingInput != null ? `${existingInput}, ${signatureInput}` : signatureInput);
|
|
863
|
+
const existingSignature = headers.get("Signature");
|
|
864
|
+
headers.set("Signature", existingSignature != null ? `${existingSignature}, ${signature}` : signature);
|
|
690
865
|
if (span.isRecording()) {
|
|
691
866
|
span.setAttribute("http_signatures.algorithm", "rsa-v1_5-sha256");
|
|
692
867
|
span.setAttribute("http_signatures.signature", encodeHex(signatureBytes));
|
|
@@ -1145,7 +1320,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1145
1320
|
continue;
|
|
1146
1321
|
}
|
|
1147
1322
|
}
|
|
1148
|
-
if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.
|
|
1323
|
+
if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.some((c) => c.value === "content-digest")) {
|
|
1149
1324
|
const contentDigestHeader = request.headers.get("Content-Digest");
|
|
1150
1325
|
if (!contentDigestHeader) {
|
|
1151
1326
|
logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
|
|
@@ -1215,7 +1390,8 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1215
1390
|
const verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
|
|
1216
1391
|
if (verified) return {
|
|
1217
1392
|
verified: true,
|
|
1218
|
-
key
|
|
1393
|
+
key,
|
|
1394
|
+
signatureLabel: sigName
|
|
1219
1395
|
};
|
|
1220
1396
|
else if (cached) {
|
|
1221
1397
|
logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
|
|
@@ -1304,12 +1480,63 @@ async function doubleKnock(request, identity, options = {}) {
|
|
|
1304
1480
|
body
|
|
1305
1481
|
});
|
|
1306
1482
|
} else if (response.status === 400 || response.status === 401 || response.status > 401) {
|
|
1307
|
-
const
|
|
1308
|
-
getLogger([
|
|
1483
|
+
const logger = getLogger([
|
|
1309
1484
|
"fedify",
|
|
1310
1485
|
"sig",
|
|
1311
1486
|
"http"
|
|
1312
|
-
])
|
|
1487
|
+
]);
|
|
1488
|
+
const acceptSigHeader = response.headers.get("Accept-Signature");
|
|
1489
|
+
if (acceptSigHeader != null) {
|
|
1490
|
+
const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
|
|
1491
|
+
const localKeyId = identity.keyId.href;
|
|
1492
|
+
const localAlg = "rsa-v1_5-sha256";
|
|
1493
|
+
let fulfilled = false;
|
|
1494
|
+
let challengeRequest;
|
|
1495
|
+
for (const entry of entries) {
|
|
1496
|
+
const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
|
|
1497
|
+
if (rfc9421 == null) continue;
|
|
1498
|
+
logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
|
|
1499
|
+
label: rfc9421.label,
|
|
1500
|
+
components: rfc9421.components
|
|
1501
|
+
});
|
|
1502
|
+
try {
|
|
1503
|
+
challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
|
|
1504
|
+
spec: "rfc9421",
|
|
1505
|
+
tracerProvider,
|
|
1506
|
+
body,
|
|
1507
|
+
rfc9421
|
|
1508
|
+
});
|
|
1509
|
+
fulfilled = true;
|
|
1510
|
+
} catch (error) {
|
|
1511
|
+
logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
|
|
1512
|
+
label: entry.label,
|
|
1513
|
+
error
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
if (fulfilled && challengeRequest != null) {
|
|
1518
|
+
signedRequest = challengeRequest;
|
|
1519
|
+
log?.(signedRequest);
|
|
1520
|
+
response = await fetch(signedRequest, {
|
|
1521
|
+
redirect: "manual",
|
|
1522
|
+
signal
|
|
1523
|
+
});
|
|
1524
|
+
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
1525
|
+
const location = response.headers.get("Location");
|
|
1526
|
+
return doubleKnock(createRedirectRequest(request, location, body), identity, {
|
|
1527
|
+
...options,
|
|
1528
|
+
body
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
if (fulfilled && response.status < 300) {
|
|
1533
|
+
await specDeterminer?.rememberSpec(origin, "rfc9421");
|
|
1534
|
+
return response;
|
|
1535
|
+
}
|
|
1536
|
+
if (fulfilled && response.status !== 400 && response.status !== 401) return response;
|
|
1537
|
+
}
|
|
1538
|
+
const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
|
|
1539
|
+
logger.debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
|
|
1313
1540
|
spec: firstTrySpec,
|
|
1314
1541
|
secondSpec: spec,
|
|
1315
1542
|
status: response.status,
|
|
@@ -1364,4 +1591,4 @@ function timingSafeEqual(a, b) {
|
|
|
1364
1591
|
}
|
|
1365
1592
|
|
|
1366
1593
|
//#endregion
|
|
1367
|
-
export { deno_default, doubleKnock, exportJwk, fetchKey, fetchKeyDetailed, generateCryptoKeyPair, importJwk, signRequest, validateCryptoKey, verifyRequest, verifyRequestDetailed };
|
|
1594
|
+
export { deno_default, doubleKnock, exportJwk, fetchKey, fetchKeyDetailed, formatAcceptSignature, fulfillAcceptSignature, generateCryptoKeyPair, importJwk, parseAcceptSignature, parseRfc9421SignatureInput, signRequest, validateAcceptSignature, validateCryptoKey, verifyRequest, verifyRequestDetailed };
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { URLPattern } from "urlpattern-polyfill";
|
|
4
4
|
globalThis.addEventListener = () => {};
|
|
5
5
|
|
|
6
|
-
import { deno_default } from "./deno-
|
|
6
|
+
import { deno_default } from "./deno-BYv1FXyT.js";
|
|
7
7
|
import { Activity, getTypeId } from "@fedify/vocab";
|
|
8
8
|
import { getLogger } from "@logtape/logtape";
|
|
9
9
|
import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { URLPattern } from "urlpattern-polyfill";
|
|
4
4
|
globalThis.addEventListener = () => {};
|
|
5
5
|
|
|
6
|
-
import { deno_default } from "./deno-
|
|
6
|
+
import { deno_default } from "./deno-BYv1FXyT.js";
|
|
7
7
|
import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
8
8
|
import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
9
9
|
import { getLogger } from "@logtape/logtape";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Temporal } from "@js-temporal/polyfill";
|
|
3
3
|
import { URLPattern } from "urlpattern-polyfill";
|
|
4
4
|
|
|
5
|
-
import { doubleKnock, validateCryptoKey } from "./http-
|
|
5
|
+
import { doubleKnock, validateCryptoKey } from "./http-WrV4DdQ1.js";
|
|
6
6
|
import { getLogger } from "@logtape/logtape";
|
|
7
7
|
import { curry } from "es-toolkit";
|
|
8
8
|
import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
4
4
|
|
|
5
5
|
const require_chunk = require('./chunk-CGaQZ11T.cjs');
|
|
6
|
-
const require_http = require('./http-
|
|
6
|
+
const require_http = require('./http-Cjdbgipx.cjs');
|
|
7
7
|
const __logtape_logtape = require_chunk.__toESM(require("@logtape/logtape"));
|
|
8
8
|
const es_toolkit = require_chunk.__toESM(require("es-toolkit"));
|
|
9
9
|
const __fedify_vocab_runtime = require_chunk.__toESM(require("@fedify/vocab-runtime"));
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
import { URLPattern } from "urlpattern-polyfill";
|
|
4
4
|
globalThis.addEventListener = () => {};
|
|
5
5
|
|
|
6
|
-
import { deno_default } from "./deno-
|
|
7
|
-
import { fetchKey, validateCryptoKey } from "./key-
|
|
6
|
+
import { deno_default } from "./deno-BYv1FXyT.js";
|
|
7
|
+
import { fetchKey, validateCryptoKey } from "./key-BSOrewQw.js";
|
|
8
8
|
import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
|
|
9
9
|
import { getDocumentLoader } from "@fedify/vocab-runtime";
|
|
10
10
|
import { getLogger } from "@logtape/logtape";
|
|
11
11
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
12
12
|
import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
|
|
13
13
|
import { encodeHex } from "byte-encodings/hex";
|
|
14
|
-
import jsonld from "jsonld";
|
|
14
|
+
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
15
15
|
|
|
16
16
|
//#region src/sig/ld.ts
|
|
17
17
|
const logger = getLogger([
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
const { Temporal } = require("@js-temporal/polyfill");
|
|
3
|
+
const { URLPattern } = require("urlpattern-polyfill");
|
|
4
|
+
|
|
5
|
+
require('./transformers-3g8GZwkZ.cjs');
|
|
6
|
+
require('./http-Cjdbgipx.cjs');
|
|
7
|
+
const require_middleware = require('./middleware-PGDxr3nC.cjs');
|
|
8
|
+
require('./proof-DFqGzNZi.cjs');
|
|
9
|
+
require('./types-Cd_hszr_.cjs');
|
|
10
|
+
require('./kv-cache-sn8V-LU_.cjs');
|
|
11
|
+
|
|
12
|
+
exports.FederationImpl = require_middleware.FederationImpl;
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import { URLPattern } from "urlpattern-polyfill";
|
|
4
4
|
|
|
5
5
|
import "./transformers-C3FLHUd6.js";
|
|
6
|
-
import "./http-
|
|
7
|
-
import { ContextImpl, FederationImpl, InboxContextImpl, KvSpecDeterminer, createFederation } from "./middleware-
|
|
8
|
-
import "./proof-
|
|
6
|
+
import "./http-WrV4DdQ1.js";
|
|
7
|
+
import { ContextImpl, FederationImpl, InboxContextImpl, KvSpecDeterminer, createFederation } from "./middleware-C_89nqvv.js";
|
|
8
|
+
import "./proof-DgU0YpXY.js";
|
|
9
9
|
import "./types-C93Ob9cU.js";
|
|
10
|
-
import "./kv-cache-
|
|
10
|
+
import "./kv-cache-D0a-g8yG.js";
|
|
11
11
|
|
|
12
12
|
export { FederationImpl };
|