@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
|
@@ -7,14 +7,14 @@ const __logtape_logtape = require_chunk.__toESM(require("@logtape/logtape"));
|
|
|
7
7
|
const __fedify_vocab = require_chunk.__toESM(require("@fedify/vocab"));
|
|
8
8
|
const __opentelemetry_api = require_chunk.__toESM(require("@opentelemetry/api"));
|
|
9
9
|
const byte_encodings_hex = require_chunk.__toESM(require("byte-encodings/hex"));
|
|
10
|
+
const structured_field_values = require_chunk.__toESM(require("structured-field-values"));
|
|
10
11
|
const __opentelemetry_semantic_conventions = require_chunk.__toESM(require("@opentelemetry/semantic-conventions"));
|
|
11
12
|
const byte_encodings_base64 = require_chunk.__toESM(require("byte-encodings/base64"));
|
|
12
|
-
const structured_field_values = require_chunk.__toESM(require("structured-field-values"));
|
|
13
13
|
const __fedify_vocab_runtime = require_chunk.__toESM(require("@fedify/vocab-runtime"));
|
|
14
14
|
|
|
15
15
|
//#region deno.json
|
|
16
16
|
var name = "@fedify/fedify";
|
|
17
|
-
var version = "2.1.0-dev.
|
|
17
|
+
var version = "2.1.0-dev.599+1ff26884";
|
|
18
18
|
var license = "MIT";
|
|
19
19
|
var exports$1 = {
|
|
20
20
|
".": "./src/mod.ts",
|
|
@@ -36,7 +36,6 @@ var imports = {
|
|
|
36
36
|
"fetch-mock": "npm:fetch-mock@^12.5.2",
|
|
37
37
|
"json-canon": "npm:json-canon@^1.0.1",
|
|
38
38
|
"jsonld": "npm:jsonld@^9.0.0",
|
|
39
|
-
"multicodec": "npm:multicodec@^3.2.1",
|
|
40
39
|
"pkijs": "npm:pkijs@^3.3.3",
|
|
41
40
|
"structured-field-values": "npm:structured-field-values@^2.0.4",
|
|
42
41
|
"uri-template-router": "npm:uri-template-router@^1.0.0",
|
|
@@ -127,6 +126,140 @@ var deno_default = {
|
|
|
127
126
|
tasks
|
|
128
127
|
};
|
|
129
128
|
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/sig/accept.ts
|
|
131
|
+
/**
|
|
132
|
+
* Parses an `Accept-Signature` header value (RFC 9421 §5.1) into an
|
|
133
|
+
* array of {@link AcceptSignatureMember} objects.
|
|
134
|
+
*
|
|
135
|
+
* The `Accept-Signature` field is a Dictionary Structured Field
|
|
136
|
+
* (RFC 8941 §3.2). Each dictionary member describes a single
|
|
137
|
+
* requested message signature.
|
|
138
|
+
*
|
|
139
|
+
* On parse failure (malformed or empty header), returns an empty array.
|
|
140
|
+
*
|
|
141
|
+
* @param header The raw `Accept-Signature` header value string.
|
|
142
|
+
* @returns An array of parsed members. Empty if the header is
|
|
143
|
+
* malformed or empty.
|
|
144
|
+
* @since 2.1.0
|
|
145
|
+
*/
|
|
146
|
+
function parseAcceptSignature(header) {
|
|
147
|
+
try {
|
|
148
|
+
return parseEachSignature((0, structured_field_values.decodeDict)(header));
|
|
149
|
+
} catch {
|
|
150
|
+
(0, __logtape_logtape.getLogger)([
|
|
151
|
+
"fedify",
|
|
152
|
+
"sig",
|
|
153
|
+
"http"
|
|
154
|
+
]).warn("Failed to parse Accept-Signature header: {header}", { header });
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const compactObject = (obj) => Object.fromEntries(Object.entries(obj).filter(([_, v]) => v !== void 0));
|
|
159
|
+
const parseEachSignature = (dict) => Object.entries(dict).filter(([_, item]) => Array.isArray(item.value)).map(([label, item]) => ({
|
|
160
|
+
label,
|
|
161
|
+
components: item.value.filter((subitem) => typeof subitem.value === "string").map((subitem) => ({
|
|
162
|
+
value: subitem.value,
|
|
163
|
+
params: subitem.params ?? {}
|
|
164
|
+
})),
|
|
165
|
+
parameters: compactParams(item)
|
|
166
|
+
}));
|
|
167
|
+
const compactParams = (item) => {
|
|
168
|
+
const { keyid, alg, created, expires, nonce, tag } = item.params ?? {};
|
|
169
|
+
return compactObject({
|
|
170
|
+
keyid: stringOrUndefined(keyid),
|
|
171
|
+
alg: stringOrUndefined(alg),
|
|
172
|
+
created: trueOrUndefined(created),
|
|
173
|
+
expires: trueOrUndefined(expires),
|
|
174
|
+
nonce: stringOrUndefined(nonce),
|
|
175
|
+
tag: stringOrUndefined(tag)
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
const stringOrUndefined = (v) => typeof v === "string" ? v : void 0;
|
|
179
|
+
const trueOrUndefined = (v) => v === true ? true : void 0;
|
|
180
|
+
/**
|
|
181
|
+
* Serializes an array of {@link AcceptSignatureMember} objects into an
|
|
182
|
+
* `Accept-Signature` header value string (RFC 9421 §5.1).
|
|
183
|
+
*
|
|
184
|
+
* The output is a Dictionary Structured Field (RFC 8941 §3.2).
|
|
185
|
+
*
|
|
186
|
+
* @param members The members to serialize.
|
|
187
|
+
* @returns The serialized header value string.
|
|
188
|
+
* @since 2.1.0
|
|
189
|
+
*/
|
|
190
|
+
function formatAcceptSignature(members) {
|
|
191
|
+
const items = members.map((member) => [member.label, new structured_field_values.Item(compToItems(member), compactParameters(member))]);
|
|
192
|
+
return (0, structured_field_values.encodeDict)(Object.fromEntries(items));
|
|
193
|
+
}
|
|
194
|
+
const compToItems = (member) => member.components.map((c) => new structured_field_values.Item(c.value, c.params));
|
|
195
|
+
const compactParameters = (member) => {
|
|
196
|
+
const { keyid, alg, created, expires, nonce, tag } = member.parameters;
|
|
197
|
+
return compactObject({
|
|
198
|
+
keyid,
|
|
199
|
+
alg,
|
|
200
|
+
created,
|
|
201
|
+
expires,
|
|
202
|
+
nonce,
|
|
203
|
+
tag
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Filters out {@link AcceptSignatureMember} entries whose covered
|
|
208
|
+
* components include response-only identifiers (`@status`) that are
|
|
209
|
+
* not applicable to request-target messages, as required by
|
|
210
|
+
* [RFC 9421 §5](https://www.rfc-editor.org/rfc/rfc9421#section-5).
|
|
211
|
+
*
|
|
212
|
+
* A warning is logged for each discarded entry.
|
|
213
|
+
*
|
|
214
|
+
* @param members The parsed `Accept-Signature` entries to validate.
|
|
215
|
+
* @returns Only entries that are valid for request-target messages.
|
|
216
|
+
* @since 2.1.0
|
|
217
|
+
*/
|
|
218
|
+
function validateAcceptSignature(members) {
|
|
219
|
+
const logger = (0, __logtape_logtape.getLogger)([
|
|
220
|
+
"fedify",
|
|
221
|
+
"sig",
|
|
222
|
+
"http"
|
|
223
|
+
]);
|
|
224
|
+
return members.filter((member) => {
|
|
225
|
+
if (member.components.every((c) => c.value !== "@status")) return true;
|
|
226
|
+
logLabel(logger, member.label);
|
|
227
|
+
return false;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
const logLabel = (logger, label) => logger.warn("Discarding Accept-Signature member {label}: covered components include response-only identifier @status.", { label });
|
|
231
|
+
/**
|
|
232
|
+
* Attempts to translate an {@link AcceptSignatureMember} challenge into
|
|
233
|
+
* RFC 9421 signing options that the local signer can fulfill.
|
|
234
|
+
*
|
|
235
|
+
* Returns `null` if the challenge cannot be fulfilled—for example, if
|
|
236
|
+
* the requested `alg` or `keyid` is incompatible with the local key.
|
|
237
|
+
*
|
|
238
|
+
* Safety constraints:
|
|
239
|
+
* - `alg`: only honored if it matches `localAlg`.
|
|
240
|
+
* - `keyid`: only honored if it matches `localKeyId`.
|
|
241
|
+
* - `components`: passed through exactly as requested, per RFC 9421 §5.2.
|
|
242
|
+
* - `nonce`, `tag`, and `expires` are passed through directly.
|
|
243
|
+
*
|
|
244
|
+
* @param entry The challenge entry from the `Accept-Signature` header.
|
|
245
|
+
* @param localKeyId The local key identifier (e.g., the actor key URL).
|
|
246
|
+
* @param localAlg The algorithm of the local private key
|
|
247
|
+
* (e.g., `"rsa-v1_5-sha256"`).
|
|
248
|
+
* @returns Signing options if the challenge can be fulfilled, or `null`.
|
|
249
|
+
* @since 2.1.0
|
|
250
|
+
*/
|
|
251
|
+
function fulfillAcceptSignature(entry, localKeyId, localAlg) {
|
|
252
|
+
if (entry.parameters.alg != null && entry.parameters.alg !== localAlg) return null;
|
|
253
|
+
if (entry.parameters.keyid != null && entry.parameters.keyid !== localKeyId) return null;
|
|
254
|
+
return {
|
|
255
|
+
label: entry.label,
|
|
256
|
+
components: entry.components,
|
|
257
|
+
nonce: entry.parameters.nonce,
|
|
258
|
+
tag: entry.parameters.tag,
|
|
259
|
+
expires: entry.parameters.expires
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
130
263
|
//#endregion
|
|
131
264
|
//#region src/sig/key.ts
|
|
132
265
|
/**
|
|
@@ -494,7 +627,7 @@ async function signRequest(request, privateKey, keyId, options = {}) {
|
|
|
494
627
|
try {
|
|
495
628
|
const spec = options.spec ?? "draft-cavage-http-signatures-12";
|
|
496
629
|
let signed;
|
|
497
|
-
if (spec === "rfc9421") signed = await signRequestRfc9421(request, privateKey, keyId, span, options.currentTime, options.body);
|
|
630
|
+
if (spec === "rfc9421") signed = await signRequestRfc9421(request, privateKey, keyId, span, options.currentTime, options.body, options.rfc9421);
|
|
498
631
|
else signed = await signRequestDraft(request, privateKey, keyId, span, options.currentTime, options.body);
|
|
499
632
|
if (span.isRecording()) {
|
|
500
633
|
span.setAttribute(__opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_METHOD, signed.method);
|
|
@@ -542,7 +675,19 @@ async function signRequestDraft(request, privateKey, keyId, span, currentTime, b
|
|
|
542
675
|
});
|
|
543
676
|
}
|
|
544
677
|
function formatRfc9421SignatureParameters(params) {
|
|
545
|
-
return
|
|
678
|
+
return Array.from(iterRfc9421(params)).join(";");
|
|
679
|
+
}
|
|
680
|
+
function* iterRfc9421(params) {
|
|
681
|
+
yield `alg="${params.algorithm}"`;
|
|
682
|
+
yield `keyid="${params.keyId.href}"`;
|
|
683
|
+
yield `created=${params.created}`;
|
|
684
|
+
if (params.expires != null) yield `expires=${params.expires}`;
|
|
685
|
+
if (params.nonce != null) yield `nonce="${escapeSfString(params.nonce)}"`;
|
|
686
|
+
if (params.tag != null) yield `tag="${escapeSfString(params.tag)}"`;
|
|
687
|
+
}
|
|
688
|
+
const escapeSfString = (value) => value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
689
|
+
function formatComponentId(component) {
|
|
690
|
+
return (0, structured_field_values.encodeItem)(new structured_field_values.Item(component.value, component.params));
|
|
546
691
|
}
|
|
547
692
|
/**
|
|
548
693
|
* Creates a signature base for a request according to RFC 9421.
|
|
@@ -553,30 +698,31 @@ function formatRfc9421SignatureParameters(params) {
|
|
|
553
698
|
*/
|
|
554
699
|
function createRfc9421SignatureBase(request, components, parameters) {
|
|
555
700
|
const url = new URL(request.url);
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
if (
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
else if (component === "@query") value = url.search.startsWith("?") ? url.search.slice(1) : url.search;
|
|
566
|
-
else if (component === "@query-param") throw new Error("@query-param requires a parameter name");
|
|
567
|
-
else if (component === "@status") throw new Error("@status is only valid for responses");
|
|
568
|
-
else if (component.startsWith("@")) throw new Error(`Unsupported derived component: ${component}`);
|
|
569
|
-
else {
|
|
570
|
-
const header = request.headers.get(component);
|
|
571
|
-
if (header == null) throw new Error(`Missing header: ${component}`);
|
|
572
|
-
value = header;
|
|
573
|
-
}
|
|
574
|
-
baseComponents.push(`"${component}": ${value}`);
|
|
575
|
-
}
|
|
576
|
-
const sigComponents = components.map((c) => `"${c}"`).join(" ");
|
|
577
|
-
baseComponents.push(`"@signature-params": (${sigComponents});${parameters}`);
|
|
578
|
-
return baseComponents.join("\n");
|
|
701
|
+
return components.map((component) => {
|
|
702
|
+
const id = formatComponentId(component);
|
|
703
|
+
const derived = derivedComponents[component.value]?.(request, url);
|
|
704
|
+
if (derived != null) return `${id}: ${derived}`;
|
|
705
|
+
if (component.value.startsWith("@")) throw new Error(`Unsupported derived component: ${component.value}`);
|
|
706
|
+
const header = request.headers.get(component.value);
|
|
707
|
+
if (header == null) throw new Error(`Missing header: ${component.value}`);
|
|
708
|
+
return `${id}: ${header}`;
|
|
709
|
+
}).concat([`"@signature-params": (${components.map((c) => formatComponentId(c)).join(" ")});${parameters}`]).join("\n");
|
|
579
710
|
}
|
|
711
|
+
const derivedComponents = {
|
|
712
|
+
"@method": (request) => request.method.toUpperCase(),
|
|
713
|
+
"@target-uri": (_, url) => url.href,
|
|
714
|
+
"@authority": (_, url) => url.host,
|
|
715
|
+
"@scheme": (_, url) => url.protocol.slice(0, -1),
|
|
716
|
+
"@request-target": (request, url) => `${request.method.toLowerCase()} ${url.pathname}${url.search}`,
|
|
717
|
+
"@path": (_, url) => url.pathname,
|
|
718
|
+
"@query": (_, { search }) => search.startsWith("?") ? search.slice(1) : search,
|
|
719
|
+
"@query-param": () => {
|
|
720
|
+
throw new Error("@query-param requires a parameter name");
|
|
721
|
+
},
|
|
722
|
+
"@status": () => {
|
|
723
|
+
throw new Error("@status is only valid for responses");
|
|
724
|
+
}
|
|
725
|
+
};
|
|
580
726
|
/**
|
|
581
727
|
* Formats a signature using rfc9421 format.
|
|
582
728
|
* @param signature The raw signature bytes.
|
|
@@ -584,9 +730,9 @@ function createRfc9421SignatureBase(request, components, parameters) {
|
|
|
584
730
|
* @param parameters The signature parameters.
|
|
585
731
|
* @returns The formatted signature string.
|
|
586
732
|
*/
|
|
587
|
-
function formatRfc9421Signature(signature, components, parameters) {
|
|
588
|
-
const signatureInputValue =
|
|
589
|
-
const signatureValue =
|
|
733
|
+
function formatRfc9421Signature(signature, components, parameters, label = "sig1") {
|
|
734
|
+
const signatureInputValue = `${label}=(${components.map((c) => formatComponentId(c)).join(" ")});${parameters}`;
|
|
735
|
+
const signatureValue = `${label}=:${(0, byte_encodings_base64.encodeBase64)(signature)}:`;
|
|
590
736
|
return [signatureInputValue, signatureValue];
|
|
591
737
|
}
|
|
592
738
|
/**
|
|
@@ -612,12 +758,17 @@ function parseRfc9421SignatureInput(signatureInput) {
|
|
|
612
758
|
const result = {};
|
|
613
759
|
for (const [label, item] of Object.entries(dict)) {
|
|
614
760
|
if (!Array.isArray(item.value) || typeof item.params.keyid !== "string" || typeof item.params.created !== "number") continue;
|
|
615
|
-
const components = item.value.
|
|
761
|
+
const components = item.value.filter((subitem) => typeof subitem.value === "string").map((subitem) => ({
|
|
762
|
+
value: subitem.value,
|
|
763
|
+
params: subitem.params ?? {}
|
|
764
|
+
}));
|
|
616
765
|
const params = (0, structured_field_values.encodeItem)(new structured_field_values.Item(0, item.params));
|
|
617
766
|
result[label] = {
|
|
618
767
|
keyId: item.params.keyid,
|
|
619
768
|
alg: item.params.alg,
|
|
620
769
|
created: item.params.created,
|
|
770
|
+
nonce: typeof item.params.nonce === "string" ? item.params.nonce : void 0,
|
|
771
|
+
tag: typeof item.params.tag === "string" ? item.params.tag : void 0,
|
|
621
772
|
components,
|
|
622
773
|
parameters: params.slice(params.indexOf(";") + 1)
|
|
623
774
|
};
|
|
@@ -648,7 +799,7 @@ function parseRfc9421Signature(signature) {
|
|
|
648
799
|
for (const [key, value] of Object.entries(dict)) if (value.value instanceof Uint8Array) result[key] = value.value;
|
|
649
800
|
return result;
|
|
650
801
|
}
|
|
651
|
-
async function signRequestRfc9421(request, privateKey, keyId, span, currentTime, bodyBuffer) {
|
|
802
|
+
async function signRequestRfc9421(request, privateKey, keyId, span, currentTime, bodyBuffer, rfc9421Options) {
|
|
652
803
|
if (privateKey.algorithm.name !== "RSASSA-PKCS1-v1_5") throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
|
|
653
804
|
const url = new URL(request.url);
|
|
654
805
|
const body = bodyBuffer !== void 0 ? bodyBuffer : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
|
|
@@ -662,18 +813,40 @@ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime,
|
|
|
662
813
|
currentTime ??= Temporal.Now.instant();
|
|
663
814
|
const created = currentTime.epochMilliseconds / 1e3 | 0;
|
|
664
815
|
if (!headers.has("Date")) headers.set("Date", new Date(currentTime.toString()).toUTCString());
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
816
|
+
const label = rfc9421Options?.label ?? "sig1";
|
|
817
|
+
const components = [...rfc9421Options?.components ?? [
|
|
818
|
+
{
|
|
819
|
+
value: "@method",
|
|
820
|
+
params: {}
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
value: "@target-uri",
|
|
824
|
+
params: {}
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
value: "@authority",
|
|
828
|
+
params: {}
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
value: "host",
|
|
832
|
+
params: {}
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
value: "date",
|
|
836
|
+
params: {}
|
|
837
|
+
}
|
|
838
|
+
], ...body != null ? [{
|
|
839
|
+
value: "content-digest",
|
|
840
|
+
params: {}
|
|
841
|
+
}] : []];
|
|
842
|
+
const expires = rfc9421Options?.expires === true ? (currentTime.epochMilliseconds / 1e3 | 0) + 3600 : void 0;
|
|
673
843
|
const signatureParams = formatRfc9421SignatureParameters({
|
|
674
844
|
algorithm: "rsa-v1_5-sha256",
|
|
675
845
|
keyId,
|
|
676
|
-
created
|
|
846
|
+
created,
|
|
847
|
+
expires,
|
|
848
|
+
nonce: rfc9421Options?.nonce,
|
|
849
|
+
tag: rfc9421Options?.tag
|
|
677
850
|
});
|
|
678
851
|
let signatureBase;
|
|
679
852
|
try {
|
|
@@ -685,9 +858,11 @@ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime,
|
|
|
685
858
|
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.`);
|
|
686
859
|
}
|
|
687
860
|
const signatureBytes = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", privateKey, new TextEncoder().encode(signatureBase));
|
|
688
|
-
const [signatureInput, signature] = formatRfc9421Signature(signatureBytes, components, signatureParams);
|
|
689
|
-
headers.
|
|
690
|
-
headers.set("Signature",
|
|
861
|
+
const [signatureInput, signature] = formatRfc9421Signature(signatureBytes, components, signatureParams, label);
|
|
862
|
+
const existingInput = headers.get("Signature-Input");
|
|
863
|
+
headers.set("Signature-Input", existingInput != null ? `${existingInput}, ${signatureInput}` : signatureInput);
|
|
864
|
+
const existingSignature = headers.get("Signature");
|
|
865
|
+
headers.set("Signature", existingSignature != null ? `${existingSignature}, ${signature}` : signature);
|
|
691
866
|
if (span.isRecording()) {
|
|
692
867
|
span.setAttribute("http_signatures.algorithm", "rsa-v1_5-sha256");
|
|
693
868
|
span.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(signatureBytes));
|
|
@@ -1146,7 +1321,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1146
1321
|
continue;
|
|
1147
1322
|
}
|
|
1148
1323
|
}
|
|
1149
|
-
if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.
|
|
1324
|
+
if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.some((c) => c.value === "content-digest")) {
|
|
1150
1325
|
const contentDigestHeader = request.headers.get("Content-Digest");
|
|
1151
1326
|
if (!contentDigestHeader) {
|
|
1152
1327
|
logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
|
|
@@ -1216,7 +1391,8 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1216
1391
|
const verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
|
|
1217
1392
|
if (verified) return {
|
|
1218
1393
|
verified: true,
|
|
1219
|
-
key
|
|
1394
|
+
key,
|
|
1395
|
+
signatureLabel: sigName
|
|
1220
1396
|
};
|
|
1221
1397
|
else if (cached) {
|
|
1222
1398
|
logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
|
|
@@ -1305,12 +1481,63 @@ async function doubleKnock(request, identity, options = {}) {
|
|
|
1305
1481
|
body
|
|
1306
1482
|
});
|
|
1307
1483
|
} else if (response.status === 400 || response.status === 401 || response.status > 401) {
|
|
1308
|
-
const
|
|
1309
|
-
(0, __logtape_logtape.getLogger)([
|
|
1484
|
+
const logger = (0, __logtape_logtape.getLogger)([
|
|
1310
1485
|
"fedify",
|
|
1311
1486
|
"sig",
|
|
1312
1487
|
"http"
|
|
1313
|
-
])
|
|
1488
|
+
]);
|
|
1489
|
+
const acceptSigHeader = response.headers.get("Accept-Signature");
|
|
1490
|
+
if (acceptSigHeader != null) {
|
|
1491
|
+
const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
|
|
1492
|
+
const localKeyId = identity.keyId.href;
|
|
1493
|
+
const localAlg = "rsa-v1_5-sha256";
|
|
1494
|
+
let fulfilled = false;
|
|
1495
|
+
let challengeRequest;
|
|
1496
|
+
for (const entry of entries) {
|
|
1497
|
+
const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
|
|
1498
|
+
if (rfc9421 == null) continue;
|
|
1499
|
+
logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
|
|
1500
|
+
label: rfc9421.label,
|
|
1501
|
+
components: rfc9421.components
|
|
1502
|
+
});
|
|
1503
|
+
try {
|
|
1504
|
+
challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
|
|
1505
|
+
spec: "rfc9421",
|
|
1506
|
+
tracerProvider,
|
|
1507
|
+
body,
|
|
1508
|
+
rfc9421
|
|
1509
|
+
});
|
|
1510
|
+
fulfilled = true;
|
|
1511
|
+
} catch (error) {
|
|
1512
|
+
logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
|
|
1513
|
+
label: entry.label,
|
|
1514
|
+
error
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
if (fulfilled && challengeRequest != null) {
|
|
1519
|
+
signedRequest = challengeRequest;
|
|
1520
|
+
log?.(signedRequest);
|
|
1521
|
+
response = await fetch(signedRequest, {
|
|
1522
|
+
redirect: "manual",
|
|
1523
|
+
signal
|
|
1524
|
+
});
|
|
1525
|
+
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
1526
|
+
const location = response.headers.get("Location");
|
|
1527
|
+
return doubleKnock(createRedirectRequest(request, location, body), identity, {
|
|
1528
|
+
...options,
|
|
1529
|
+
body
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
if (fulfilled && response.status < 300) {
|
|
1534
|
+
await specDeterminer?.rememberSpec(origin, "rfc9421");
|
|
1535
|
+
return response;
|
|
1536
|
+
}
|
|
1537
|
+
if (fulfilled && response.status !== 400 && response.status !== 401) return response;
|
|
1538
|
+
}
|
|
1539
|
+
const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
|
|
1540
|
+
logger.debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
|
|
1314
1541
|
spec: firstTrySpec,
|
|
1315
1542
|
secondSpec: spec,
|
|
1316
1543
|
status: response.status,
|
|
@@ -1395,6 +1622,18 @@ Object.defineProperty(exports, 'fetchKeyDetailed', {
|
|
|
1395
1622
|
return fetchKeyDetailed;
|
|
1396
1623
|
}
|
|
1397
1624
|
});
|
|
1625
|
+
Object.defineProperty(exports, 'formatAcceptSignature', {
|
|
1626
|
+
enumerable: true,
|
|
1627
|
+
get: function () {
|
|
1628
|
+
return formatAcceptSignature;
|
|
1629
|
+
}
|
|
1630
|
+
});
|
|
1631
|
+
Object.defineProperty(exports, 'fulfillAcceptSignature', {
|
|
1632
|
+
enumerable: true,
|
|
1633
|
+
get: function () {
|
|
1634
|
+
return fulfillAcceptSignature;
|
|
1635
|
+
}
|
|
1636
|
+
});
|
|
1398
1637
|
Object.defineProperty(exports, 'generateCryptoKeyPair', {
|
|
1399
1638
|
enumerable: true,
|
|
1400
1639
|
get: function () {
|
|
@@ -1407,12 +1646,30 @@ Object.defineProperty(exports, 'importJwk', {
|
|
|
1407
1646
|
return importJwk;
|
|
1408
1647
|
}
|
|
1409
1648
|
});
|
|
1649
|
+
Object.defineProperty(exports, 'parseAcceptSignature', {
|
|
1650
|
+
enumerable: true,
|
|
1651
|
+
get: function () {
|
|
1652
|
+
return parseAcceptSignature;
|
|
1653
|
+
}
|
|
1654
|
+
});
|
|
1655
|
+
Object.defineProperty(exports, 'parseRfc9421SignatureInput', {
|
|
1656
|
+
enumerable: true,
|
|
1657
|
+
get: function () {
|
|
1658
|
+
return parseRfc9421SignatureInput;
|
|
1659
|
+
}
|
|
1660
|
+
});
|
|
1410
1661
|
Object.defineProperty(exports, 'signRequest', {
|
|
1411
1662
|
enumerable: true,
|
|
1412
1663
|
get: function () {
|
|
1413
1664
|
return signRequest;
|
|
1414
1665
|
}
|
|
1415
1666
|
});
|
|
1667
|
+
Object.defineProperty(exports, 'validateAcceptSignature', {
|
|
1668
|
+
enumerable: true,
|
|
1669
|
+
get: function () {
|
|
1670
|
+
return validateAcceptSignature;
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1416
1673
|
Object.defineProperty(exports, 'validateCryptoKey', {
|
|
1417
1674
|
enumerable: true,
|
|
1418
1675
|
get: function () {
|