@fedify/fedify 2.1.0-dev.565 → 2.1.0-dev.592

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/accept-D7sAxyNa.js +143 -0
  2. package/dist/{assert_rejects-Ce45JcFg.js → assert_rejects-0h7I2Esa.js} +1 -1
  3. package/dist/{builder-Deoi2N2z.js → builder-B24i8eYp.js} +3 -3
  4. package/dist/compat/mod.d.cts +3 -3
  5. package/dist/compat/mod.d.ts +3 -3
  6. package/dist/compat/transformers.test.js +17 -16
  7. package/dist/{context-DL0cPpPV.d.cts → context-BcqA-0BL.d.cts} +52 -2
  8. package/dist/{context--RwChtri.d.ts → context-DyJjQQ_H.d.ts} +52 -2
  9. package/dist/{deno-CEdy89j9.js → deno-OR506Yti.js} +1 -2
  10. package/dist/{docloader-CL1QPJzN.js → docloader-BG_pP2fW.js} +2 -2
  11. package/dist/federation/builder.test.js +7 -7
  12. package/dist/federation/collection.test.js +5 -5
  13. package/dist/federation/handler.test.js +806 -26
  14. package/dist/federation/idempotency.test.js +22 -21
  15. package/dist/federation/inbox.test.js +3 -3
  16. package/dist/federation/keycache.test.js +1 -1
  17. package/dist/federation/kv.test.js +4 -4
  18. package/dist/federation/middleware.test.js +22 -21
  19. package/dist/federation/mod.cjs +4 -4
  20. package/dist/federation/mod.d.cts +4 -4
  21. package/dist/federation/mod.d.ts +4 -4
  22. package/dist/federation/mod.js +4 -4
  23. package/dist/federation/mq.test.js +4 -4
  24. package/dist/federation/negotiation.test.js +5 -5
  25. package/dist/federation/retry.test.js +2 -2
  26. package/dist/federation/router.test.js +4 -4
  27. package/dist/federation/send.test.js +11 -10
  28. package/dist/federation/webfinger.test.js +22 -21
  29. package/dist/{http-Dm9n1mRe.js → http-BUCxbGks.js} +144 -49
  30. package/dist/{http-DsqqmkXi.d.cts → http-BudnHZE2.d.cts} +229 -1
  31. package/dist/{http-iDlaLy8a.cjs → http-CaXARmaJ.cjs} +307 -50
  32. package/dist/{http-BbfOqHGG.d.ts → http-Dax_FIBo.d.ts} +229 -1
  33. package/dist/{http-VpqmUjje.js → http-DePHjWKP.js} +278 -51
  34. package/dist/{inbox-CMtnW0RE.js → inbox-D_LU1opv.js} +1 -1
  35. package/dist/{key-B0yADkL8.js → key-Cx3Tx_In.js} +1 -1
  36. package/dist/{kv-cache-551Om14-.cjs → kv-cache-CYTDBChd.cjs} +1 -1
  37. package/dist/{kv-cache-BSATpUtX.js → kv-cache-DizRqYX4.js} +1 -1
  38. package/dist/{ld-BBmbv1nb.js → ld-CLMJw_iX.js} +3 -3
  39. package/dist/{middleware-Cx0tTbX1.js → middleware--uATyG9i.js} +95 -18
  40. package/dist/{middleware-DpdPMZII.js → middleware-4fo4pEtA.js} +4 -4
  41. package/dist/{middleware-D11GYoP-.cjs → middleware-9YDezkYJ.cjs} +94 -17
  42. package/dist/middleware-C2PqSUaA.js +27 -0
  43. package/dist/middleware-DNY45l5T.cjs +12 -0
  44. package/dist/{middleware-Cldp2YSv.js → middleware-DzICTgdC.js} +113 -34
  45. package/dist/{mod-DE8MYisy.d.cts → mod-B7QkWzrL.d.cts} +1 -1
  46. package/dist/{mod-DKG0ovjR.d.cts → mod-Bx9jcLB8.d.cts} +1 -1
  47. package/dist/{mod-CFBU2OT3.d.cts → mod-Coe7KEgX.d.cts} +1 -1
  48. package/dist/{mod-BugwI0JN.d.ts → mod-Cs2dYEwI.d.ts} +1 -1
  49. package/dist/{mod-DcfFNgYf.d.ts → mod-D6MdymW7.d.ts} +1 -1
  50. package/dist/{mod-CvxylbuV.d.ts → mod-D6dOd--H.d.ts} +1 -1
  51. package/dist/{mod-Z7lIaCfo.d.ts → mod-SMHOMNpZ.d.ts} +1 -1
  52. package/dist/{mod-Dp0kK0hO.d.cts → mod-em2Il1eD.d.cts} +1 -1
  53. package/dist/mod.cjs +12 -4
  54. package/dist/mod.d.cts +8 -8
  55. package/dist/mod.d.ts +8 -8
  56. package/dist/mod.js +9 -5
  57. package/dist/nodeinfo/client.test.js +4 -4
  58. package/dist/nodeinfo/handler.test.js +22 -21
  59. package/dist/nodeinfo/types.test.js +4 -4
  60. package/dist/otel/exporter.test.js +4 -4
  61. package/dist/{owner-C1ZyG4NL.js → owner-D5J299vd.js} +1 -1
  62. package/dist/{proof-wclcUq0C.js → proof-BBLHhWMC.js} +2 -2
  63. package/dist/{proof-CgK60TcQ.cjs → proof-BVl5IgbN.cjs} +3 -3
  64. package/dist/{proof-DnRq8s8f.js → proof-CiCp_mCG.js} +2 -2
  65. package/dist/{send-DNJyYRVU.js → send-2b0Fn9cn.js} +2 -2
  66. package/dist/sig/accept.test.d.ts +3 -0
  67. package/dist/sig/accept.test.js +451 -0
  68. package/dist/sig/http.test.js +452 -27
  69. package/dist/sig/key.test.js +7 -7
  70. package/dist/sig/ld.test.js +6 -6
  71. package/dist/sig/mod.cjs +6 -2
  72. package/dist/sig/mod.d.cts +3 -3
  73. package/dist/sig/mod.d.ts +3 -3
  74. package/dist/sig/mod.js +3 -3
  75. package/dist/sig/owner.test.js +8 -8
  76. package/dist/sig/proof.test.js +8 -8
  77. package/dist/testing/mod.js +1 -1
  78. package/dist/utils/docloader.test.js +10 -9
  79. package/dist/utils/kv-cache.test.js +1 -1
  80. package/dist/utils/mod.cjs +2 -2
  81. package/dist/utils/mod.d.cts +2 -2
  82. package/dist/utils/mod.d.ts +2 -2
  83. package/dist/utils/mod.js +2 -2
  84. package/package.json +6 -7
  85. package/dist/middleware-BDr0P6dx.cjs +0 -12
  86. package/dist/middleware-BZ8WpBo6.js +0 -26
  87. /package/dist/{assert_not_equals-C80BG-_5.js → assert_not_equals-f3m3epl3.js} +0 -0
  88. /package/dist/{assert_throws-BNXdRGWP.js → assert_throws-rjdMBf31.js} +0 -0
  89. /package/dist/{collection-CcnIw1qY.js → collection-CSzG2j1P.js} +0 -0
  90. /package/dist/{context-pa9aIrwp.js → context-Aqenou7c.js} +0 -0
  91. /package/dist/{keycache-C7k8s1Bk.js → keycache-CpGWAUbj.js} +0 -0
  92. /package/dist/{keys-ZbcByPg9.js → keys-BFve7QQv.js} +0 -0
  93. /package/dist/{kv-cache-El7We5sy.js → kv-cache-Bw2F2ABq.js} +0 -0
  94. /package/dist/{negotiation-5NPJL6zp.js → negotiation-BlAuS_nr.js} +0 -0
  95. /package/dist/{retry-D4GJ670a.js → retry-mqLf4b-R.js} +0 -0
  96. /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.565+b4d238a9";
17
+ var version = "2.1.0-dev.592+6c1f6e6f";
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 `alg="${params.algorithm}";keyid="${params.keyId.href}";created=${params.created}`;
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
- const baseComponents = [];
557
- for (const component of components) {
558
- let value;
559
- if (component === "@method") value = request.method.toUpperCase();
560
- else if (component === "@target-uri") value = request.url;
561
- else if (component === "@authority") value = url.host;
562
- else if (component === "@scheme") value = url.protocol.slice(0, -1);
563
- else if (component === "@request-target") value = `${request.method.toLowerCase()} ${url.pathname}${url.search}`;
564
- else if (component === "@path") value = url.pathname;
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 = `sig1=("${components.join("\" \"")}");${parameters}`;
589
- const signatureValue = `sig1=:${(0, byte_encodings_base64.encodeBase64)(signature)}:`;
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.map((subitem) => subitem.value).filter((v) => typeof v === "string");
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 components = [
666
- "@method",
667
- "@target-uri",
668
- "@authority",
669
- "host",
670
- "date"
671
- ];
672
- if (body != null) components.push("content-digest");
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.set("Signature-Input", signatureInput);
690
- headers.set("Signature", 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.includes("content-digest")) {
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 spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
1309
- (0, __logtape_logtape.getLogger)([
1484
+ const logger = (0, __logtape_logtape.getLogger)([
1310
1485
  "fedify",
1311
1486
  "sig",
1312
1487
  "http"
1313
- ]).debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
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 () {