@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
@@ -3,8 +3,9 @@
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
  globalThis.addEventListener = () => {};
5
5
 
6
- import { deno_default } from "./deno-CEdy89j9.js";
7
- import { fetchKeyDetailed, validateCryptoKey } from "./key-B0yADkL8.js";
6
+ import { deno_default } from "./deno-OR506Yti.js";
7
+ import { fulfillAcceptSignature, parseAcceptSignature, validateAcceptSignature } from "./accept-D7sAxyNa.js";
8
+ import { fetchKeyDetailed, validateCryptoKey } from "./key-Cx3Tx_In.js";
8
9
  import { CryptographicKey } from "@fedify/vocab";
9
10
  import { getLogger } from "@logtape/logtape";
10
11
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -31,7 +32,7 @@ async function signRequest(request, privateKey, keyId, options = {}) {
31
32
  try {
32
33
  const spec = options.spec ?? "draft-cavage-http-signatures-12";
33
34
  let signed;
34
- if (spec === "rfc9421") signed = await signRequestRfc9421(request, privateKey, keyId, span, options.currentTime, options.body);
35
+ if (spec === "rfc9421") signed = await signRequestRfc9421(request, privateKey, keyId, span, options.currentTime, options.body, options.rfc9421);
35
36
  else signed = await signRequestDraft(request, privateKey, keyId, span, options.currentTime, options.body);
36
37
  if (span.isRecording()) {
37
38
  span.setAttribute(ATTR_HTTP_REQUEST_METHOD, signed.method);
@@ -79,7 +80,19 @@ async function signRequestDraft(request, privateKey, keyId, span, currentTime, b
79
80
  });
80
81
  }
81
82
  function formatRfc9421SignatureParameters(params) {
82
- return `alg="${params.algorithm}";keyid="${params.keyId.href}";created=${params.created}`;
83
+ return Array.from(iterRfc9421(params)).join(";");
84
+ }
85
+ function* iterRfc9421(params) {
86
+ yield `alg="${params.algorithm}"`;
87
+ yield `keyid="${params.keyId.href}"`;
88
+ yield `created=${params.created}`;
89
+ if (params.expires != null) yield `expires=${params.expires}`;
90
+ if (params.nonce != null) yield `nonce="${escapeSfString(params.nonce)}"`;
91
+ if (params.tag != null) yield `tag="${escapeSfString(params.tag)}"`;
92
+ }
93
+ const escapeSfString = (value) => value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
94
+ function formatComponentId(component) {
95
+ return encodeItem(new Item(component.value, component.params));
83
96
  }
84
97
  /**
85
98
  * Creates a signature base for a request according to RFC 9421.
@@ -90,30 +103,31 @@ function formatRfc9421SignatureParameters(params) {
90
103
  */
91
104
  function createRfc9421SignatureBase(request, components, parameters) {
92
105
  const url = new URL(request.url);
93
- const baseComponents = [];
94
- for (const component of components) {
95
- let value;
96
- if (component === "@method") value = request.method.toUpperCase();
97
- else if (component === "@target-uri") value = request.url;
98
- else if (component === "@authority") value = url.host;
99
- else if (component === "@scheme") value = url.protocol.slice(0, -1);
100
- else if (component === "@request-target") value = `${request.method.toLowerCase()} ${url.pathname}${url.search}`;
101
- else if (component === "@path") value = url.pathname;
102
- else if (component === "@query") value = url.search.startsWith("?") ? url.search.slice(1) : url.search;
103
- else if (component === "@query-param") throw new Error("@query-param requires a parameter name");
104
- else if (component === "@status") throw new Error("@status is only valid for responses");
105
- else if (component.startsWith("@")) throw new Error(`Unsupported derived component: ${component}`);
106
- else {
107
- const header = request.headers.get(component);
108
- if (header == null) throw new Error(`Missing header: ${component}`);
109
- value = header;
110
- }
111
- baseComponents.push(`"${component}": ${value}`);
112
- }
113
- const sigComponents = components.map((c) => `"${c}"`).join(" ");
114
- baseComponents.push(`"@signature-params": (${sigComponents});${parameters}`);
115
- return baseComponents.join("\n");
106
+ return components.map((component) => {
107
+ const id = formatComponentId(component);
108
+ const derived = derivedComponents[component.value]?.(request, url);
109
+ if (derived != null) return `${id}: ${derived}`;
110
+ if (component.value.startsWith("@")) throw new Error(`Unsupported derived component: ${component.value}`);
111
+ const header = request.headers.get(component.value);
112
+ if (header == null) throw new Error(`Missing header: ${component.value}`);
113
+ return `${id}: ${header}`;
114
+ }).concat([`"@signature-params": (${components.map((c) => formatComponentId(c)).join(" ")});${parameters}`]).join("\n");
116
115
  }
116
+ const derivedComponents = {
117
+ "@method": (request) => request.method.toUpperCase(),
118
+ "@target-uri": (_, url) => url.href,
119
+ "@authority": (_, url) => url.host,
120
+ "@scheme": (_, url) => url.protocol.slice(0, -1),
121
+ "@request-target": (request, url) => `${request.method.toLowerCase()} ${url.pathname}${url.search}`,
122
+ "@path": (_, url) => url.pathname,
123
+ "@query": (_, { search }) => search.startsWith("?") ? search.slice(1) : search,
124
+ "@query-param": () => {
125
+ throw new Error("@query-param requires a parameter name");
126
+ },
127
+ "@status": () => {
128
+ throw new Error("@status is only valid for responses");
129
+ }
130
+ };
117
131
  /**
118
132
  * Formats a signature using rfc9421 format.
119
133
  * @param signature The raw signature bytes.
@@ -121,9 +135,9 @@ function createRfc9421SignatureBase(request, components, parameters) {
121
135
  * @param parameters The signature parameters.
122
136
  * @returns The formatted signature string.
123
137
  */
124
- function formatRfc9421Signature(signature, components, parameters) {
125
- const signatureInputValue = `sig1=("${components.join("\" \"")}");${parameters}`;
126
- const signatureValue = `sig1=:${encodeBase64(signature)}:`;
138
+ function formatRfc9421Signature(signature, components, parameters, label = "sig1") {
139
+ const signatureInputValue = `${label}=(${components.map((c) => formatComponentId(c)).join(" ")});${parameters}`;
140
+ const signatureValue = `${label}=:${encodeBase64(signature)}:`;
127
141
  return [signatureInputValue, signatureValue];
128
142
  }
129
143
  /**
@@ -149,12 +163,17 @@ function parseRfc9421SignatureInput(signatureInput) {
149
163
  const result = {};
150
164
  for (const [label, item] of Object.entries(dict)) {
151
165
  if (!Array.isArray(item.value) || typeof item.params.keyid !== "string" || typeof item.params.created !== "number") continue;
152
- const components = item.value.map((subitem) => subitem.value).filter((v) => typeof v === "string");
166
+ const components = item.value.filter((subitem) => typeof subitem.value === "string").map((subitem) => ({
167
+ value: subitem.value,
168
+ params: subitem.params ?? {}
169
+ }));
153
170
  const params = encodeItem(new Item(0, item.params));
154
171
  result[label] = {
155
172
  keyId: item.params.keyid,
156
173
  alg: item.params.alg,
157
174
  created: item.params.created,
175
+ nonce: typeof item.params.nonce === "string" ? item.params.nonce : void 0,
176
+ tag: typeof item.params.tag === "string" ? item.params.tag : void 0,
158
177
  components,
159
178
  parameters: params.slice(params.indexOf(";") + 1)
160
179
  };
@@ -185,7 +204,7 @@ function parseRfc9421Signature(signature) {
185
204
  for (const [key, value] of Object.entries(dict)) if (value.value instanceof Uint8Array) result[key] = value.value;
186
205
  return result;
187
206
  }
188
- async function signRequestRfc9421(request, privateKey, keyId, span, currentTime, bodyBuffer) {
207
+ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime, bodyBuffer, rfc9421Options) {
189
208
  if (privateKey.algorithm.name !== "RSASSA-PKCS1-v1_5") throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
190
209
  const url = new URL(request.url);
191
210
  const body = bodyBuffer !== void 0 ? bodyBuffer : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
@@ -199,18 +218,40 @@ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime,
199
218
  currentTime ??= Temporal.Now.instant();
200
219
  const created = currentTime.epochMilliseconds / 1e3 | 0;
201
220
  if (!headers.has("Date")) headers.set("Date", new Date(currentTime.toString()).toUTCString());
202
- const components = [
203
- "@method",
204
- "@target-uri",
205
- "@authority",
206
- "host",
207
- "date"
208
- ];
209
- if (body != null) components.push("content-digest");
221
+ const label = rfc9421Options?.label ?? "sig1";
222
+ const components = [...rfc9421Options?.components ?? [
223
+ {
224
+ value: "@method",
225
+ params: {}
226
+ },
227
+ {
228
+ value: "@target-uri",
229
+ params: {}
230
+ },
231
+ {
232
+ value: "@authority",
233
+ params: {}
234
+ },
235
+ {
236
+ value: "host",
237
+ params: {}
238
+ },
239
+ {
240
+ value: "date",
241
+ params: {}
242
+ }
243
+ ], ...body != null ? [{
244
+ value: "content-digest",
245
+ params: {}
246
+ }] : []];
247
+ const expires = rfc9421Options?.expires === true ? (currentTime.epochMilliseconds / 1e3 | 0) + 3600 : void 0;
210
248
  const signatureParams = formatRfc9421SignatureParameters({
211
249
  algorithm: "rsa-v1_5-sha256",
212
250
  keyId,
213
- created
251
+ created,
252
+ expires,
253
+ nonce: rfc9421Options?.nonce,
254
+ tag: rfc9421Options?.tag
214
255
  });
215
256
  let signatureBase;
216
257
  try {
@@ -222,9 +263,11 @@ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime,
222
263
  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.`);
223
264
  }
224
265
  const signatureBytes = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", privateKey, new TextEncoder().encode(signatureBase));
225
- const [signatureInput, signature] = formatRfc9421Signature(signatureBytes, components, signatureParams);
226
- headers.set("Signature-Input", signatureInput);
227
- headers.set("Signature", signature);
266
+ const [signatureInput, signature] = formatRfc9421Signature(signatureBytes, components, signatureParams, label);
267
+ const existingInput = headers.get("Signature-Input");
268
+ headers.set("Signature-Input", existingInput != null ? `${existingInput}, ${signatureInput}` : signatureInput);
269
+ const existingSignature = headers.get("Signature");
270
+ headers.set("Signature", existingSignature != null ? `${existingSignature}, ${signature}` : signature);
228
271
  if (span.isRecording()) {
229
272
  span.setAttribute("http_signatures.algorithm", "rsa-v1_5-sha256");
230
273
  span.setAttribute("http_signatures.signature", encodeHex(signatureBytes));
@@ -683,7 +726,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
683
726
  continue;
684
727
  }
685
728
  }
686
- if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.includes("content-digest")) {
729
+ if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.some((c) => c.value === "content-digest")) {
687
730
  const contentDigestHeader = request.headers.get("Content-Digest");
688
731
  if (!contentDigestHeader) {
689
732
  logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
@@ -753,7 +796,8 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
753
796
  const verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
754
797
  if (verified) return {
755
798
  verified: true,
756
- key
799
+ key,
800
+ signatureLabel: sigName
757
801
  };
758
802
  else if (cached) {
759
803
  logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
@@ -842,12 +886,63 @@ async function doubleKnock(request, identity, options = {}) {
842
886
  body
843
887
  });
844
888
  } else if (response.status === 400 || response.status === 401 || response.status > 401) {
845
- const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
846
- getLogger([
889
+ const logger = getLogger([
847
890
  "fedify",
848
891
  "sig",
849
892
  "http"
850
- ]).debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
893
+ ]);
894
+ const acceptSigHeader = response.headers.get("Accept-Signature");
895
+ if (acceptSigHeader != null) {
896
+ const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
897
+ const localKeyId = identity.keyId.href;
898
+ const localAlg = "rsa-v1_5-sha256";
899
+ let fulfilled = false;
900
+ let challengeRequest;
901
+ for (const entry of entries) {
902
+ const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
903
+ if (rfc9421 == null) continue;
904
+ logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
905
+ label: rfc9421.label,
906
+ components: rfc9421.components
907
+ });
908
+ try {
909
+ challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
910
+ spec: "rfc9421",
911
+ tracerProvider,
912
+ body,
913
+ rfc9421
914
+ });
915
+ fulfilled = true;
916
+ } catch (error) {
917
+ logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
918
+ label: entry.label,
919
+ error
920
+ });
921
+ }
922
+ }
923
+ if (fulfilled && challengeRequest != null) {
924
+ signedRequest = challengeRequest;
925
+ log?.(signedRequest);
926
+ response = await fetch(signedRequest, {
927
+ redirect: "manual",
928
+ signal
929
+ });
930
+ if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
931
+ const location = response.headers.get("Location");
932
+ return doubleKnock(createRedirectRequest(request, location, body), identity, {
933
+ ...options,
934
+ body
935
+ });
936
+ }
937
+ }
938
+ if (fulfilled && response.status < 300) {
939
+ await specDeterminer?.rememberSpec(origin, "rfc9421");
940
+ return response;
941
+ }
942
+ if (fulfilled && response.status !== 400 && response.status !== 401) return response;
943
+ }
944
+ const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
945
+ logger.debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
851
946
  spec: firstTrySpec,
852
947
  secondSpec: spec,
853
948
  status: response.status,
@@ -151,6 +151,194 @@ interface KeyCache {
151
151
  set(keyId: URL, key: CryptographicKey | Multikey | null): Promise<void>;
152
152
  }
153
153
  //#endregion
154
+ //#region src/sig/accept.d.ts
155
+ /**
156
+ * Signature metadata parameters that may appear in an
157
+ * `Accept-Signature` member, as defined in
158
+ * [RFC 9421 §5.1](https://www.rfc-editor.org/rfc/rfc9421#section-5.1).
159
+ *
160
+ * @since 2.1.0
161
+ */
162
+ interface AcceptSignatureParameters {
163
+ /**
164
+ * If present, the signer is requested to use the indicated key
165
+ * material to create the target signature.
166
+ */
167
+ keyid?: string;
168
+ /**
169
+ * If present, the signer is requested to use the indicated algorithm
170
+ * from the HTTP Signature Algorithms registry.
171
+ */
172
+ alg?: string;
173
+ /**
174
+ * If `true`, the signer is requested to generate and include a
175
+ * creation timestamp. This parameter has no associated value in the
176
+ * wire format.
177
+ */
178
+ created?: true;
179
+ /**
180
+ * If `true`, the signer is requested to generate and include an
181
+ * expiration timestamp. This parameter has no associated value in
182
+ * the wire format.
183
+ */
184
+ expires?: true;
185
+ /**
186
+ * If present, the signer is requested to include this value as the
187
+ * signature nonce in the target signature.
188
+ */
189
+ nonce?: string;
190
+ /**
191
+ * If present, the signer is requested to include this value as the
192
+ * signature tag in the target signature.
193
+ */
194
+ tag?: string;
195
+ }
196
+ /**
197
+ * A single covered component identifier from an `Accept-Signature` inner list,
198
+ * as defined in [RFC 9421 §2.1](https://www.rfc-editor.org/rfc/rfc9421#section-2.1)
199
+ * and [§5.1](https://www.rfc-editor.org/rfc/rfc9421#section-5.1).
200
+ *
201
+ * RFC 9421 §5.1 requires that the list of component identifiers includes
202
+ * *all applicable component parameters*. Parameters such as `;sf`, `;bs`,
203
+ * `;req`, `;tr`, `;name`, and `;key` narrow the meaning of a component
204
+ * identifier and MUST be preserved exactly as received so that the signer
205
+ * can cover the same components the verifier requested.
206
+ *
207
+ * Examples:
208
+ * - `{ value: "@method", params: {} }`
209
+ * - `{ value: "content-type", params: { sf: true } }`
210
+ * - `{ value: "@query-param", params: { name: "foo" } }`
211
+ *
212
+ * @since 2.1.0
213
+ */
214
+ interface AcceptSignatureComponent {
215
+ /**
216
+ * The component identifier name (e.g., `"@method"`, `"content-digest"`,
217
+ * `"@query-param"`).
218
+ */
219
+ value: string;
220
+ /**
221
+ * Component parameters attached to this identifier (e.g., `{ sf: true }`,
222
+ * `{ name: "foo" }`). An empty object means no parameters were present.
223
+ * Parameters MUST NOT be dropped; doing so would cause the signer to cover
224
+ * a different component than the verifier requested.
225
+ */
226
+ params: Record<string, unknown>;
227
+ }
228
+ /**
229
+ * Represents a single member of the `Accept-Signature` Dictionary
230
+ * Structured Field, as defined in
231
+ * [RFC 9421 §5.1](https://www.rfc-editor.org/rfc/rfc9421#section-5.1).
232
+ *
233
+ * @since 2.1.0
234
+ */
235
+ interface AcceptSignatureMember {
236
+ /**
237
+ * The label that uniquely identifies the requested message signature
238
+ * within the context of the target HTTP message (e.g., `"sig1"`).
239
+ */
240
+ label: string;
241
+ /**
242
+ * The exact list of covered component identifiers requested for the target
243
+ * signature, including all applicable component parameters, as required by
244
+ * [RFC 9421 §5.1](https://www.rfc-editor.org/rfc/rfc9421#section-5.1).
245
+ *
246
+ * Each element is an {@link AcceptSignatureComponent} that preserves
247
+ * both the identifier name and any parameters (e.g., `;sf`, `;name="foo"`).
248
+ * The signer MUST cover exactly these components—with their parameters—when
249
+ * fulfilling the challenge.
250
+ */
251
+ components: AcceptSignatureComponent[];
252
+ /**
253
+ * Optional signature metadata parameters requested by the verifier.
254
+ */
255
+ parameters: AcceptSignatureParameters;
256
+ }
257
+ /**
258
+ * Parses an `Accept-Signature` header value (RFC 9421 §5.1) into an
259
+ * array of {@link AcceptSignatureMember} objects.
260
+ *
261
+ * The `Accept-Signature` field is a Dictionary Structured Field
262
+ * (RFC 8941 §3.2). Each dictionary member describes a single
263
+ * requested message signature.
264
+ *
265
+ * On parse failure (malformed or empty header), returns an empty array.
266
+ *
267
+ * @param header The raw `Accept-Signature` header value string.
268
+ * @returns An array of parsed members. Empty if the header is
269
+ * malformed or empty.
270
+ * @since 2.1.0
271
+ */
272
+ declare function parseAcceptSignature(header: string): AcceptSignatureMember[];
273
+ /**
274
+ * Serializes an array of {@link AcceptSignatureMember} objects into an
275
+ * `Accept-Signature` header value string (RFC 9421 §5.1).
276
+ *
277
+ * The output is a Dictionary Structured Field (RFC 8941 §3.2).
278
+ *
279
+ * @param members The members to serialize.
280
+ * @returns The serialized header value string.
281
+ * @since 2.1.0
282
+ */
283
+ declare function formatAcceptSignature(members: AcceptSignatureMember[]): string;
284
+ /**
285
+ * Filters out {@link AcceptSignatureMember} entries whose covered
286
+ * components include response-only identifiers (`@status`) that are
287
+ * not applicable to request-target messages, as required by
288
+ * [RFC 9421 §5](https://www.rfc-editor.org/rfc/rfc9421#section-5).
289
+ *
290
+ * A warning is logged for each discarded entry.
291
+ *
292
+ * @param members The parsed `Accept-Signature` entries to validate.
293
+ * @returns Only entries that are valid for request-target messages.
294
+ * @since 2.1.0
295
+ */
296
+ declare function validateAcceptSignature(members: AcceptSignatureMember[]): AcceptSignatureMember[];
297
+ /**
298
+ * The result of {@link fulfillAcceptSignature}. This can be used directly
299
+ * as the `rfc9421` option of {@link SignRequestOptions}.
300
+ * @since 2.1.0
301
+ */
302
+ interface FulfillAcceptSignatureResult {
303
+ /** The label for the signature. */
304
+ label: string;
305
+ /**
306
+ * The merged set of covered component identifiers, including all component
307
+ * parameters, ready to be passed to the signer.
308
+ */
309
+ components: AcceptSignatureComponent[];
310
+ /** The nonce requested by the challenge, if any. */
311
+ nonce?: string;
312
+ /** The tag requested by the challenge, if any. */
313
+ tag?: string;
314
+ /**
315
+ * If `true`, the challenger requested that the signer generate and include
316
+ * an expiration timestamp in the signature parameters.
317
+ */
318
+ expires?: true;
319
+ }
320
+ /**
321
+ * Attempts to translate an {@link AcceptSignatureMember} challenge into
322
+ * RFC 9421 signing options that the local signer can fulfill.
323
+ *
324
+ * Returns `null` if the challenge cannot be fulfilled—for example, if
325
+ * the requested `alg` or `keyid` is incompatible with the local key.
326
+ *
327
+ * Safety constraints:
328
+ * - `alg`: only honored if it matches `localAlg`.
329
+ * - `keyid`: only honored if it matches `localKeyId`.
330
+ * - `components`: passed through exactly as requested, per RFC 9421 §5.2.
331
+ * - `nonce`, `tag`, and `expires` are passed through directly.
332
+ *
333
+ * @param entry The challenge entry from the `Accept-Signature` header.
334
+ * @param localKeyId The local key identifier (e.g., the actor key URL).
335
+ * @param localAlg The algorithm of the local private key
336
+ * (e.g., `"rsa-v1_5-sha256"`).
337
+ * @returns Signing options if the challenge can be fulfilled, or `null`.
338
+ * @since 2.1.0
339
+ */
340
+ declare function fulfillAcceptSignature(entry: AcceptSignatureMember, localKeyId: string, localAlg: string): FulfillAcceptSignatureResult | null;
341
+ //#endregion
154
342
  //#region src/sig/http.d.ts
155
343
  /**
156
344
  * The standard to use for signing and verifying HTTP signatures.
@@ -184,6 +372,45 @@ interface SignRequestOptions {
184
372
  * is used.
185
373
  */
186
374
  tracerProvider?: TracerProvider;
375
+ /**
376
+ * Options specific to the RFC 9421 signing path. These options are
377
+ * ignored when `spec` is `"draft-cavage-http-signatures-12"`.
378
+ * @since 2.1.0
379
+ */
380
+ rfc9421?: Rfc9421SignRequestOptions;
381
+ }
382
+ /**
383
+ * Options for customizing the RFC 9421 signature label, covered components,
384
+ * and metadata parameters. These are typically derived from an
385
+ * `Accept-Signature` challenge.
386
+ * @since 2.1.0
387
+ */
388
+ interface Rfc9421SignRequestOptions {
389
+ /**
390
+ * The label for the signature in `Signature-Input` and `Signature` headers.
391
+ * @default `"sig1"`
392
+ */
393
+ label?: string;
394
+ /**
395
+ * The covered component identifiers. When omitted, the default set
396
+ * `["@method", "@target-uri", "@authority", "host", "date"]`
397
+ * (plus `"content-digest"` when a body is present) is used.
398
+ */
399
+ components?: AcceptSignatureComponent[];
400
+ /**
401
+ * A nonce value to include in the signature parameters.
402
+ */
403
+ nonce?: string;
404
+ /**
405
+ * A tag value to include in the signature parameters.
406
+ */
407
+ tag?: string;
408
+ /**
409
+ * If `true`, an expiration timestamp is generated and included in the
410
+ * signature parameters. The expiration time defaults to one hour after
411
+ * the signature creation time.
412
+ */
413
+ expires?: true;
187
414
  }
188
415
  /**
189
416
  * Signs a request using the given private key.
@@ -259,6 +486,7 @@ type VerifyRequestFailureReason = {
259
486
  type VerifyRequestDetailedResult = {
260
487
  readonly verified: true;
261
488
  readonly key: CryptographicKey;
489
+ readonly signatureLabel?: string;
262
490
  } | {
263
491
  readonly verified: false;
264
492
  readonly reason: VerifyRequestFailureReason;
@@ -313,4 +541,4 @@ interface HttpMessageSignaturesSpecDeterminer {
313
541
  * @since 1.6.0
314
542
  */
315
543
  //#endregion
316
- export { FetchKeyDetailedResult, FetchKeyErrorResult, FetchKeyOptions, FetchKeyResult, HttpMessageSignaturesSpec, HttpMessageSignaturesSpecDeterminer, KeyCache, SignRequestOptions, VerifyRequestDetailedResult, VerifyRequestFailureReason, VerifyRequestOptions, exportJwk, fetchKey, fetchKeyDetailed, generateCryptoKeyPair, importJwk, signRequest, verifyRequest, verifyRequestDetailed };
544
+ export { AcceptSignatureMember, AcceptSignatureParameters, FetchKeyDetailedResult, FetchKeyErrorResult, FetchKeyOptions, FetchKeyResult, FulfillAcceptSignatureResult, HttpMessageSignaturesSpec, HttpMessageSignaturesSpecDeterminer, KeyCache, Rfc9421SignRequestOptions, SignRequestOptions, VerifyRequestDetailedResult, VerifyRequestFailureReason, VerifyRequestOptions, exportJwk, fetchKey, fetchKeyDetailed, formatAcceptSignature, fulfillAcceptSignature, generateCryptoKeyPair, importJwk, parseAcceptSignature, signRequest, validateAcceptSignature, verifyRequest, verifyRequestDetailed };