@adcp/sdk 16.0.0-beta.6 → 16.0.0-beta.8

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 (137) hide show
  1. package/README.md +17 -0
  2. package/dist/lib/conformance/index.d.mts +2 -0
  3. package/dist/lib/conformance/index.d.ts +2 -0
  4. package/dist/lib/conformance/index.js +3 -0
  5. package/dist/lib/conformance/index.mjs +2 -0
  6. package/dist/lib/conformance/property-lists.d.mts +14 -0
  7. package/dist/lib/conformance/property-lists.d.ts +14 -0
  8. package/dist/lib/conformance/property-lists.js +45 -0
  9. package/dist/lib/conformance/property-lists.mjs +21 -0
  10. package/dist/lib/conformance/runConformance.js +20 -1
  11. package/dist/lib/conformance/runConformance.mjs +20 -1
  12. package/dist/lib/conformance/types.d.mts +3 -0
  13. package/dist/lib/conformance/types.d.ts +3 -0
  14. package/dist/lib/core/SingleAgentClient.d.mts +13 -3
  15. package/dist/lib/core/SingleAgentClient.d.ts +13 -3
  16. package/dist/lib/core/SingleAgentClient.js +109 -21
  17. package/dist/lib/core/SingleAgentClient.mjs +112 -22
  18. package/dist/lib/core/TaskExecutor.d.mts +1 -0
  19. package/dist/lib/core/TaskExecutor.d.ts +1 -0
  20. package/dist/lib/core/TaskExecutor.js +48 -34
  21. package/dist/lib/core/TaskExecutor.mjs +49 -35
  22. package/dist/lib/identifiers.d.mts +53 -0
  23. package/dist/lib/identifiers.d.ts +53 -0
  24. package/dist/lib/identifiers.js +212 -0
  25. package/dist/lib/identifiers.mjs +163 -0
  26. package/dist/lib/identity-3.3/revocation.js +5 -4
  27. package/dist/lib/identity-3.3/revocation.mjs +5 -4
  28. package/dist/lib/index.d.mts +3 -1
  29. package/dist/lib/index.d.ts +3 -1
  30. package/dist/lib/index.js +2 -0
  31. package/dist/lib/index.mjs +1 -0
  32. package/dist/lib/media-buy/index.d.mts +1 -1
  33. package/dist/lib/media-buy/index.d.ts +1 -1
  34. package/dist/lib/media-buy/property-policy.d.mts +15 -1
  35. package/dist/lib/media-buy/property-policy.d.ts +15 -1
  36. package/dist/lib/media-buy/property-policy.js +135 -3
  37. package/dist/lib/media-buy/property-policy.mjs +134 -3
  38. package/dist/lib/registry/index.d.mts +12 -0
  39. package/dist/lib/registry/index.d.ts +12 -0
  40. package/dist/lib/registry/index.js +159 -5
  41. package/dist/lib/registry/index.mjs +156 -4
  42. package/dist/lib/registry/types.d.mts +23 -0
  43. package/dist/lib/registry/types.d.ts +23 -0
  44. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  45. package/dist/lib/server/auth-signature.js +23 -2
  46. package/dist/lib/server/auth-signature.mjs +26 -2
  47. package/dist/lib/server/auth.d.mts +6 -0
  48. package/dist/lib/server/auth.d.ts +6 -0
  49. package/dist/lib/server/auth.js +6 -0
  50. package/dist/lib/server/auth.mjs +6 -0
  51. package/dist/lib/server/create-adcp-server.js +19 -3
  52. package/dist/lib/server/create-adcp-server.mjs +23 -4
  53. package/dist/lib/server/decisioning/runtime/from-platform.js +6 -2
  54. package/dist/lib/server/decisioning/runtime/from-platform.mjs +6 -2
  55. package/dist/lib/server/decisioning/specialisms/lists.d.mts +4 -4
  56. package/dist/lib/server/decisioning/specialisms/lists.d.ts +4 -4
  57. package/dist/lib/server/index.d.mts +3 -0
  58. package/dist/lib/server/index.d.ts +3 -0
  59. package/dist/lib/server/index.js +8 -1
  60. package/dist/lib/server/index.mjs +4 -0
  61. package/dist/lib/server/property-lists.d.mts +36 -0
  62. package/dist/lib/server/property-lists.d.ts +36 -0
  63. package/dist/lib/server/property-lists.js +154 -0
  64. package/dist/lib/server/property-lists.mjs +129 -0
  65. package/dist/lib/server/serve.js +2 -1
  66. package/dist/lib/server/serve.mjs +2 -1
  67. package/dist/lib/server/targeting-helpers.d.mts +2 -0
  68. package/dist/lib/server/targeting-helpers.d.ts +2 -0
  69. package/dist/lib/server/targeting-helpers.js +28 -1
  70. package/dist/lib/server/targeting-helpers.mjs +18 -1
  71. package/dist/lib/server/webhook-emitter.js +1 -0
  72. package/dist/lib/server/webhook-emitter.mjs +2 -1
  73. package/dist/lib/signing/crypto.d.mts +1 -1
  74. package/dist/lib/signing/crypto.d.ts +1 -1
  75. package/dist/lib/signing/crypto.js +37 -2
  76. package/dist/lib/signing/crypto.mjs +37 -2
  77. package/dist/lib/signing/parser.d.mts +6 -0
  78. package/dist/lib/signing/parser.d.ts +6 -0
  79. package/dist/lib/signing/parser.js +7 -3
  80. package/dist/lib/signing/parser.mjs +6 -3
  81. package/dist/lib/signing/server.d.mts +1 -1
  82. package/dist/lib/signing/server.d.ts +1 -1
  83. package/dist/lib/signing/server.mjs +5 -1
  84. package/dist/lib/signing/web-bot-auth.d.mts +61 -22
  85. package/dist/lib/signing/web-bot-auth.d.ts +61 -22
  86. package/dist/lib/signing/web-bot-auth.js +286 -266
  87. package/dist/lib/signing/web-bot-auth.mjs +288 -277
  88. package/dist/lib/signing/webhook-verifier.d.mts +11 -0
  89. package/dist/lib/signing/webhook-verifier.d.ts +11 -0
  90. package/dist/lib/signing/webhook-verifier.js +11 -3
  91. package/dist/lib/signing/webhook-verifier.mjs +11 -3
  92. package/dist/lib/supply-path/verify.js +12 -2
  93. package/dist/lib/supply-path/verify.mjs +2 -2
  94. package/dist/lib/testing/client.js +3 -5
  95. package/dist/lib/testing/client.mjs +4 -6
  96. package/dist/lib/testing/compliance/comply.js +7 -2
  97. package/dist/lib/testing/compliance/comply.mjs +8 -3
  98. package/dist/lib/testing/index.d.mts +1 -1
  99. package/dist/lib/testing/index.d.ts +1 -1
  100. package/dist/lib/testing/index.js +4 -2
  101. package/dist/lib/testing/index.mjs +8 -2
  102. package/dist/lib/testing/storyboard/runner.js +12 -12
  103. package/dist/lib/testing/storyboard/runner.mjs +14 -13
  104. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  105. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  106. package/dist/lib/testing/storyboard/validations.js +23 -1
  107. package/dist/lib/testing/storyboard/validations.mjs +28 -2
  108. package/dist/lib/testing/types.d.mts +39 -0
  109. package/dist/lib/testing/types.d.ts +39 -0
  110. package/dist/lib/v2/projection/augment-response.js +6 -3
  111. package/dist/lib/v2/projection/augment-response.mjs +8 -5
  112. package/dist/lib/v2/projection/creative-delivery.js +29 -17
  113. package/dist/lib/v2/projection/creative-delivery.mjs +31 -18
  114. package/dist/lib/v2/projection/v1-to-v2.d.mts +7 -0
  115. package/dist/lib/v2/projection/v1-to-v2.d.ts +7 -0
  116. package/dist/lib/v2/projection/v1-to-v2.js +70 -2
  117. package/dist/lib/v2/projection/v1-to-v2.mjs +72 -1
  118. package/dist/lib/validation/index.d.mts +1 -1
  119. package/dist/lib/validation/index.d.ts +1 -1
  120. package/dist/lib/validation/index.js +4 -2
  121. package/dist/lib/validation/index.mjs +4 -2
  122. package/dist/lib/validation/schema-loader.d.mts +26 -0
  123. package/dist/lib/validation/schema-loader.d.ts +26 -0
  124. package/dist/lib/validation/schema-loader.js +98 -14
  125. package/dist/lib/validation/schema-loader.mjs +95 -13
  126. package/dist/lib/version.d.mts +3 -3
  127. package/dist/lib/version.d.ts +3 -3
  128. package/dist/lib/version.js +3 -3
  129. package/dist/lib/version.mjs +3 -3
  130. package/docs/TYPE-SUMMARY.md +2 -2
  131. package/docs/guides/PROPERTY-LISTS.md +264 -0
  132. package/docs/guides/SIGNING-GUIDE.md +25 -0
  133. package/docs/guides/VALIDATE-YOUR-AGENT.md +34 -0
  134. package/docs/llms.txt +2 -2
  135. package/docs/migration-14.x-rc-worksheet.md +4 -4
  136. package/docs/migration-agent-identity-3.3.md +60 -23
  137. package/package.json +1 -1
@@ -21,10 +21,13 @@ __export(web_bot_auth_exports, {
21
21
  SIGNATURE_DIRECTORY_MEDIA_TYPE: () => SIGNATURE_DIRECTORY_MEDIA_TYPE,
22
22
  SIGNATURE_DIRECTORY_PATH: () => SIGNATURE_DIRECTORY_PATH,
23
23
  SignatureDirectoryResolver: () => SignatureDirectoryResolver,
24
+ WEB_BOT_AUTH_ALGORITHMS: () => WEB_BOT_AUTH_ALGORITHMS,
24
25
  WEB_BOT_AUTH_DRAFT_REVISION: () => WEB_BOT_AUTH_DRAFT_REVISION,
25
26
  WEB_BOT_AUTH_PROFILE: () => WEB_BOT_AUTH_PROFILE,
26
27
  WebBotAuthError: () => WebBotAuthError,
27
28
  WebBotAuthUnavailableError: () => WebBotAuthUnavailableError,
29
+ advertisesOnlyWebBotAuth: () => advertisesOnlyWebBotAuth,
30
+ assertWebBotAuthSigningKey: () => assertWebBotAuthSigningKey,
28
31
  buildWebBotAuthSignatureBase: () => buildWebBotAuthSignatureBase,
29
32
  createSignedSignatureDirectory: () => createSignedSignatureDirectory,
30
33
  createWebBotAuthAuthorizationFailure: () => createWebBotAuthAuthorizationFailure,
@@ -40,9 +43,11 @@ __export(web_bot_auth_exports, {
40
43
  registerWebBotAuthRevocationCheck: () => registerWebBotAuthRevocationCheck,
41
44
  selectSignatureProfile: () => selectSignatureProfile,
42
45
  signWebBotAuth: () => signWebBotAuth,
46
+ unsignedWebBotAuthError: () => unsignedWebBotAuthError,
43
47
  verifyKeyDirectory: () => verifyKeyDirectory,
44
48
  verifySignatureDirectory: () => verifySignatureDirectory,
45
49
  verifyWebBotAuth: () => verifyWebBotAuth,
50
+ webBotAuthAcceptSignature: () => webBotAuthAcceptSignature,
46
51
  webBotAuthPrincipalFailureError: () => webBotAuthPrincipalFailureError,
47
52
  webBotAuthToolFailure: () => webBotAuthToolFailure,
48
53
  webBotAuthUnavailableResponse: () => webBotAuthUnavailableResponse
@@ -65,26 +70,42 @@ const WEB_BOT_AUTH_PROFILE = "web-bot-auth";
65
70
  const WEB_BOT_AUTH_DRAFT_REVISION = "draft-ietf-webbotauth-httpsig-protocol-00";
66
71
  const SIGNATURE_DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
67
72
  const SIGNATURE_DIRECTORY_MEDIA_TYPE = "application/http-message-signatures-directory+json";
73
+ const WEB_BOT_AUTH_ALGORITHMS = Object.freeze(["ed25519", "rsa-pss-sha512"]);
68
74
  const DIRECTORY_TAG = "http-message-signatures-directory";
69
- const DIRECTORY_CLOCK_SKEW_SECONDS = 60;
75
+ const CLOCK_SKEW_SECONDS = 60;
76
+ const MAX_LIFETIME_SECONDS = 300;
77
+ const REQUIRED_PARAMETERS = ["created", "expires", "nonce", "keyid", "alg", "tag"];
70
78
  const TEST_KIDS = /* @__PURE__ */ new Set([
71
79
  "6WsKMCObba49V3uN0vIENnk1BKAk7c9zjBJcWKyQ9_I",
72
80
  "hHurKlVBnPJ4AGiPyl6HWMhxgkIcRBzaC8qWwEtnuVI",
73
81
  "ZtaFf5jj-EzorJmZn6mX3YnmK_QyZKVLbbinzIQKOjQ",
74
82
  "1XlJ3EIte7bPOEqKQtKrKNTNy5hywTyrwFXWrMbXKqE"
75
83
  ]);
84
+ function webBotAuthAcceptSignature(label = "sig1", hasBody = true, nonce) {
85
+ const body = hasBody ? '"content-type" "content-digest" ' : "";
86
+ const nonceParam = nonce === void 0 ? "nonce" : `nonce="${nonce}"`;
87
+ return `${label}=("@method" "@target-uri" "@authority" ${body}"signature-agent";key="${label}");created;expires;${nonceParam};keyid;alg;tag="web-bot-auth"`;
88
+ }
89
+ function freshNonce() {
90
+ return (0, import_crypto.randomBytes)(64).toString("base64url");
91
+ }
92
+ function nonceBytes(nonce) {
93
+ if (typeof nonce !== "string") return void 0;
94
+ const encoding = /^[A-Za-z0-9_-]{86}$/.test(nonce) ? "base64url" : /^[A-Za-z0-9+/]{86}==$/.test(nonce) ? "base64" : void 0;
95
+ if (!encoding) return void 0;
96
+ const bytes = Buffer.from(nonce, encoding);
97
+ return bytes.length === 64 && bytes.toString(encoding) === nonce ? bytes : void 0;
98
+ }
76
99
  const NO_CHALLENGE_REASONS = /* @__PURE__ */ new Set(["key_revoked", "identity_not_bound", "profile_disabled", "authorization_failed"]);
77
100
  class WebBotAuthError extends Error {
78
101
  constructor(status, reason) {
79
102
  super(
80
- status === 400 ? "Malformed Web Bot Auth message" : status === 429 ? "Web Bot Auth rate limit" : "Web Bot Auth verification failed"
103
+ status === 400 ? "Malformed Web Bot Auth message" : status === 429 ? reason === "replayed" ? "Web Bot Auth signature or nonce reused" : "Web Bot Auth rate limit" : "Web Bot Auth verification failed"
81
104
  );
82
105
  this.status = status;
83
106
  this.reason = reason;
84
107
  this.name = "WebBotAuthError";
85
- this.headers = status === 403 && !NO_CHALLENGE_REASONS.has(reason) ? {
86
- "Accept-Signature": 'sig1=("@method" "@target-uri" "@authority" "content-type" "content-digest" "signature-agent";key="sig1");created;expires;nonce;keyid;tag="web-bot-auth"'
87
- } : status === 429 ? { "Retry-After": "60" } : {};
108
+ this.headers = status === 403 && !NO_CHALLENGE_REASONS.has(reason) ? { "Accept-Signature": webBotAuthAcceptSignature() } : status === 429 && reason === "replayed" ? { "Accept-Signature": webBotAuthAcceptSignature("sig1", true, freshNonce()) } : status === 429 ? { "Retry-After": "60" } : {};
88
109
  }
89
110
  status;
90
111
  reason;
@@ -120,6 +141,20 @@ function bad(reason) {
120
141
  function denied(reason) {
121
142
  throw new WebBotAuthError(403, reason);
122
143
  }
144
+ function challengeFor(error, label, hasBody) {
145
+ const current = error.headers["Accept-Signature"];
146
+ if (current) {
147
+ const nonce = error.status === 429 ? freshNonce() : void 0;
148
+ error.headers["Accept-Signature"] = webBotAuthAcceptSignature(label, hasBody, nonce);
149
+ }
150
+ return error;
151
+ }
152
+ function unsignedWebBotAuthError(hasBody) {
153
+ return challengeFor(new WebBotAuthError(403, "signature_required"), "sig1", hasBody);
154
+ }
155
+ function advertisesOnlyWebBotAuth(profiles) {
156
+ return !!profiles?.includes(WEB_BOT_AUTH_PROFILE) && !profiles.includes("adcp/request-signing/v1");
157
+ }
123
158
  function dictionary(value, field) {
124
159
  if (value === void 0 || value === "") return denied("headers_missing");
125
160
  if (value.length > 64 * 1024) return bad("headers");
@@ -149,18 +184,30 @@ function jwkThumbprint(jwk) {
149
184
  }
150
185
  function algorithm(jwk) {
151
186
  if (jwk.kty === "OKP" && jwk.crv === "Ed25519") return "ed25519";
152
- if (jwk.kty === "EC" && jwk.crv === "P-256") return "ecdsa-p256-sha256";
153
187
  if (jwk.kty === "RSA" && typeof jwk.n === "string" && jwk.e === "AQAB") {
154
188
  const bytes = Buffer.from(jwk.n, "base64url").length;
155
189
  if (bytes >= 256 && bytes <= 512) return "rsa-pss-sha512";
156
190
  }
157
191
  return denied("unsupported_key");
158
192
  }
159
- function inputs(headers) {
160
- const value = (0, import_canonicalize2.getHeaderValue)(headers, "signature-input");
161
- return [...dictionary(value, "Signature-Input").keys()].map((label) => {
193
+ function signingAlgorithm(jwk) {
194
+ try {
195
+ return algorithm(jwk);
196
+ } catch {
197
+ throw new TypeError(
198
+ "Web Bot Auth signs with Ed25519 or RSA-PSS-SHA512 (2048-4096 bit, e=65537) keys only; ES256 remains on the AdCP RFC 9421 profile"
199
+ );
200
+ }
201
+ }
202
+ function assertWebBotAuthSigningKey(key) {
203
+ signingAlgorithm("privateKey" in key ? key.privateKey : key.publicKey);
204
+ }
205
+ function inputs(headers, limit) {
206
+ const members = dictionary((0, import_canonicalize2.getHeaderValue)(headers, "signature-input"), "Signature-Input");
207
+ if (members.size > limit) bad("signature_input");
208
+ return [...members].map(([label, member]) => {
162
209
  try {
163
- return (0, import_parser.parseSignatureInput)(value, label);
210
+ return (0, import_parser.parseSignatureInputMember)(label, member);
164
211
  } catch {
165
212
  return bad("signature_input");
166
213
  }
@@ -170,10 +217,10 @@ function receivedAgentOrigin(value) {
170
217
  try {
171
218
  (0, import_select_agent.canonicalAgentUrl)(value);
172
219
  const origin = (0, import_canonicalize.canonicalAgentOrigin)(value);
173
- if (value !== origin || /[^\x21-\x7e]/.test(value) || value.includes("%")) return bad("agent_origin");
220
+ if (value !== origin || /[^\x21-\x7e]/.test(value) || value.includes("%")) return denied("agent_origin");
174
221
  return origin;
175
222
  } catch {
176
- return bad("agent_origin");
223
+ return denied("agent_origin");
177
224
  }
178
225
  }
179
226
  function buildWebBotAuthSignatureBase(input, request, response) {
@@ -206,10 +253,7 @@ function buildWebBotAuthSignatureBase(input, request, response) {
206
253
  if (params.has("key")) {
207
254
  const member = dictionary(value, bare).get(params.get("key"));
208
255
  if (!member) return denied("missing_member");
209
- if (Array.isArray(member[0])) {
210
- const selected = (0, import_parser.parseSignatureInput)(value, params.get("key"));
211
- value = selected.signatureParamsValue;
212
- } else value = (0, import_structured_headers.serializeItem)(member);
256
+ value = Array.isArray(member[0]) ? (0, import_structured_headers.serializeInnerList)(member) : (0, import_structured_headers.serializeItem)(member);
213
257
  }
214
258
  }
215
259
  if (value === void 0) return denied("missing_component");
@@ -222,7 +266,7 @@ function receivedTargetUri(url) {
222
266
  try {
223
267
  new URL(url);
224
268
  } catch {
225
- return bad("target_uri");
269
+ return denied("target_uri");
226
270
  }
227
271
  return url;
228
272
  }
@@ -230,7 +274,7 @@ function receivedAuthority(url) {
230
274
  try {
231
275
  return new URL(url).host.toLowerCase();
232
276
  } catch {
233
- return bad("target_uri");
277
+ return denied("target_uri");
234
278
  }
235
279
  }
236
280
  function assertDigest(headers, body) {
@@ -238,41 +282,48 @@ function assertDigest(headers, body) {
238
282
  if (!digest || !(0, import_content_digest.contentDigestUsesEncoding)(digest, "rfc8941-base64") || !(0, import_content_digest.contentDigestMatches)(digest, body))
239
283
  denied("content_digest");
240
284
  }
241
- function signatureBytes(input, request, jwk, response) {
285
+ function keyVerifier(jwk) {
242
286
  const alg = algorithm(jwk);
243
- if (input.params.alg !== void 0 && input.params.alg !== alg) denied("algorithm");
244
- const signature = (0, import_canonicalize2.getHeaderValue)(response?.headers ?? request.headers, "signature");
245
- if (signature === void 0 || signature === "") denied("headers_missing");
246
- try {
247
- return { alg, bytes: (0, import_parser.parseSignature)(signature ?? "", input.label, "rfc8941-base64").bytes };
248
- } catch {
249
- return bad("signature");
250
- }
287
+ return { algorithm: alg, verify: (data, signature) => (0, import_crypto2.verifySignatureAsync)(alg, jwk, data, signature) };
251
288
  }
252
- async function verifyBytesAsync(input, request, jwk) {
253
- const { alg, bytes } = signatureBytes(input, request, jwk);
254
- let valid = false;
255
- try {
256
- valid = await (0, import_crypto2.verifySignatureAsync)(alg, jwk, Buffer.from(buildWebBotAuthSignatureBase(input, request)), bytes);
257
- } catch (error) {
258
- if (error instanceof WebBotAuthError) throw error;
259
- }
260
- if (!valid) denied("signature_invalid");
289
+ function componentDescriptor(identifier) {
290
+ const bare = identifier.split(";", 1)[0];
291
+ const [, params] = (0, import_structured_headers.parseItem)(`"${bare}"${identifier.slice(bare.length)}`);
292
+ return (0, import_http_message_sig.component)(bare, Object.fromEntries(params));
261
293
  }
262
- function verifyBytes(input, request, jwk, response) {
263
- const { alg, bytes } = signatureBytes(input, request, jwk, response);
264
- let valid = false;
294
+ function coveredFields(components, headers) {
295
+ const names = new Set(components.filter((c) => !c.name.startsWith("@")).map((c) => c.name));
296
+ return [...names].flatMap((name) => {
297
+ const value = (0, import_canonicalize2.getHeaderValue)(headers, name);
298
+ return value === void 0 ? [] : [{ name, value }];
299
+ });
300
+ }
301
+ async function verifyWithLibrary(message, label, policy, resolveVerifier) {
265
302
  try {
266
- valid = (0, import_crypto2.verifySignature)(
267
- alg,
268
- (0, import_crypto2.jwkToPublicKey)(jwk),
269
- Buffer.from(buildWebBotAuthSignatureBase(input, request, response)),
270
- bytes
271
- );
303
+ await (0, import_http_message_sig.verifySignature)(message, {
304
+ label,
305
+ policy: { algorithms: WEB_BOT_AUTH_ALGORITHMS, clockSkew: CLOCK_SKEW_SECONDS, ...policy },
306
+ resolveVerifier
307
+ });
272
308
  } catch (error) {
273
- if (error instanceof WebBotAuthError) throw error;
309
+ if (!(0, import_http_message_sig.isSignatureError)(error)) throw error;
310
+ const cause = error.cause;
311
+ if (cause instanceof WebBotAuthError || cause instanceof WebBotAuthUnavailableError) throw cause;
312
+ switch (error.code) {
313
+ case import_http_message_sig.SignatureErrorCode.VerificationFailed:
314
+ return denied("signature_invalid");
315
+ case import_http_message_sig.SignatureErrorCode.AlgorithmMismatch:
316
+ return denied("algorithm");
317
+ case import_http_message_sig.SignatureErrorCode.PolicyViolation:
318
+ return denied("policy");
319
+ case import_http_message_sig.SignatureErrorCode.MissingField:
320
+ return denied("missing_component");
321
+ case import_http_message_sig.SignatureErrorCode.ResolverFailed:
322
+ return denied("verification_unavailable");
323
+ default:
324
+ return denied("component");
325
+ }
274
326
  }
275
- if (!valid) denied("signature_invalid");
276
327
  }
277
328
  const legacyOriginGates = /* @__PURE__ */ new WeakMap();
278
329
  function registerWebBotAuthLegacyOriginGate(resolver, gate) {
@@ -366,123 +417,127 @@ function isVerifiedWebBotSigner(value) {
366
417
  return typeof value === "object" && value !== null && verifiedSigners.has(value);
367
418
  }
368
419
  async function verifyWebBotAuth(request, options) {
420
+ const hasBody = request.body !== void 0 && request.body !== "";
421
+ let label = "sig1";
369
422
  try {
370
423
  if (!(options.directory instanceof SignatureDirectoryResolver)) denied("directory_unverified");
371
424
  const now = Math.floor((options.now ?? (() => Date.now() / 1e3))());
372
425
  if (!Number.isFinite(now)) denied("verification_unavailable");
373
- const all = inputs(request.headers);
374
- const candidates = all.filter((input) => input.params.tag === WEB_BOT_AUTH_PROFILE);
375
- const selected = [];
376
- const remaining = [...candidates];
377
- while (remaining.length) {
378
- const index = remaining.findIndex(
379
- (input) => candidates.every(
380
- (other) => !input.components.some(
381
- (c) => c === `signature-input;key="${other.label}"` || c === `signature;key="${other.label}"`
382
- ) || selected.some((done) => done.label === other.label)
383
- )
384
- );
385
- if (index < 0) denied("relay_cycle");
386
- selected.push(remaining.splice(index, 1)[0]);
426
+ const inputHeader = (0, import_canonicalize2.getHeaderValue)(request.headers, "signature-input");
427
+ const signatureHeader = (0, import_canonicalize2.getHeaderValue)(request.headers, "signature");
428
+ const agentHeader = (0, import_canonicalize2.getHeaderValue)(request.headers, "signature-agent");
429
+ const present = (value) => value !== void 0 && value !== "";
430
+ const inputMembers = present(inputHeader) ? dictionary(inputHeader, "Signature-Input") : void 0;
431
+ const signatureMembers = present(signatureHeader) ? dictionary(signatureHeader, "Signature") : void 0;
432
+ const agents = present(agentHeader) ? dictionary(agentHeader, "Signature-Agent") : void 0;
433
+ for (const member2 of inputMembers?.values() ?? [])
434
+ if (!Array.isArray(member2[0]) || member2[0].some((item) => typeof item[0] !== "string")) bad("signature_input");
435
+ for (const member2 of signatureMembers?.values() ?? [])
436
+ if (!(member2[0] instanceof ArrayBuffer) || member2[1].size !== 0) bad("signature");
437
+ for (const member2 of agents?.values() ?? []) if (typeof member2[0] !== "string") bad("signature_agent");
438
+ if (!inputMembers || !signatureMembers) return denied("headers_missing");
439
+ if (inputMembers.size !== 1 || signatureMembers.size !== 1) denied("signature_count");
440
+ label = inputMembers.keys().next().value;
441
+ if (!signatureMembers.has(label)) denied("signature_label");
442
+ try {
443
+ (0, import_parser.parseSignature)(signatureHeader, label, "rfc8941-base64");
444
+ } catch {
445
+ return bad("signature");
387
446
  }
388
- if (!selected.length || selected.length > 8) denied("profile");
389
- const agents = dictionary((0, import_canonicalize2.getHeaderValue)(request.headers, "signature-agent"), "Signature-Agent");
390
- const verified = [];
391
- const nonces = /* @__PURE__ */ new Set();
392
- for (const input of selected) {
393
- const { created, expires, nonce, keyid } = input.params;
394
- if ([created, expires, nonce, keyid].some((value) => value === void 0)) denied("parameters_missing");
395
- if (!Number.isInteger(created) || !Number.isInteger(expires) || typeof nonce !== "string" || !/^[A-Za-z0-9_+\/-]{22,256}={0,2}$/.test(nonce) || typeof keyid !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(keyid))
396
- bad("parameters");
397
- if (expires <= created || expires - created > 300 || created > now + 60 || expires < now - 60) denied("window");
398
- const agentComponents = input.components.filter(
399
- (c) => c.startsWith("signature-agent;key=") && !verified.some((s) => selected.find((i) => i.label === s.label).components.includes(c))
400
- );
401
- if (agentComponents.length !== 1) denied("agent_not_covered");
402
- const component2 = (0, import_structured_headers.parseItem)(`"${agentComponents[0].replace("signature-agent;", 'signature-agent";')}`);
403
- const memberKey = component2[1].get("key");
404
- if (typeof memberKey !== "string" || memberKey !== input.label) bad("agent_member");
405
- const member = agents.get(memberKey);
406
- if (!member) denied("agent_not_covered");
407
- if (typeof member[0] !== "string") bad("signature_agent");
408
- const type = member[1].get("type");
409
- if (type !== void 0 && type !== "directory") denied("agent_type");
410
- const origin = receivedAgentOrigin(member[0]);
411
- for (const earlier of verified) {
412
- const inner = selected.find((i) => i.label === earlier.label);
413
- const required = [
414
- ...inner.components,
415
- `signature-input;key="${inner.label}"`,
416
- `signature;key="${inner.label}"`
417
- ];
418
- if (required.some((c) => !input.components.includes(c))) denied("relay_coverage");
419
- }
420
- if (new Set(input.components).size !== input.components.length || ["@method", "@target-uri", "@authority"].some((c) => !input.components.includes(c)))
421
- denied("coverage");
422
- if (request.body !== void 0 && request.body !== "") {
423
- if (["content-type", "content-digest"].some((c) => !input.components.includes(c))) denied("body_coverage");
424
- assertDigest(request.headers, request.body);
425
- }
426
- await options.directory.resolve(keyid, origin);
427
- const checkedDirectory = options.directory.getVerifiedDirectory(origin);
428
- if (!checkedDirectory || new URL(checkedDirectory.proof.request.url).origin !== origin)
429
- denied("directory_origin");
430
- if (checkedDirectory.expires <= now) denied("directory_expired");
431
- if (!testKeysAllowed(options.allowTestKeys ?? options.directory.allowsTestKeys) && checkedDirectory.keys.some((key2) => TEST_KIDS.has(key2.kid)))
432
- denied("directory_key");
433
- const key = checkedDirectory.keys.find((key2) => key2.kid === keyid);
434
- if (!key || jwkThumbprint(key) !== keyid) denied("key");
435
- await verifyBytesAsync(input, request, key);
436
- if (options.isRevoked) await legacyRevocationCheck(options, origin, keyid);
437
- const nonceTuple = JSON.stringify([origin, nonce]);
438
- if (nonces.has(nonceTuple)) throw new WebBotAuthError(options.replayStatus ?? 429, "nonce_replayed");
439
- nonces.add(nonceTuple);
440
- const signer = Object.freeze({
441
- profile: WEB_BOT_AUTH_PROFILE,
442
- origin,
443
- keyid,
444
- label: input.label,
445
- verified_at: now
446
- });
447
- verified.push(signer);
447
+ let input;
448
+ try {
449
+ input = (0, import_parser.parseSignatureInputMember)(label, inputMembers.get(label));
450
+ } catch {
451
+ return denied("parameters");
448
452
  }
449
- for (const signer of verified) {
450
- const input = selected.find((i) => i.label === signer.label);
451
- let result;
452
- try {
453
- result = await options.replayStore.insert(
454
- signer.origin,
455
- WEB_BOT_AUTH_PROFILE,
456
- input.params.nonce,
457
- // Acceptance is inclusive of expires + 60, so the nonce must outlive it.
458
- input.params.expires + 60 - now + 1,
459
- now
460
- );
461
- } catch (cause) {
462
- throw new WebBotAuthUnavailableError("replay_unavailable", cause);
463
- }
464
- if (result !== "ok")
465
- throw new WebBotAuthError(result === "replayed" ? options.replayStatus ?? 429 : 429, result);
453
+ if (input.params.tag !== WEB_BOT_AUTH_PROFILE) denied("profile");
454
+ if (!agents || agents.size !== 1) denied(agents ? "agent_count" : "agent_missing");
455
+ const agentMember = `signature-agent;key="${label}"`;
456
+ const agentComponents = input.components.filter((c) => c.startsWith("signature-agent;"));
457
+ if (agentComponents.length !== 1 || agentComponents[0] !== agentMember) denied("agent_not_covered");
458
+ const member = agents.get(label);
459
+ if (!member) denied("agent_not_covered");
460
+ const type = member[1].get("type");
461
+ if (type !== void 0 && type !== "directory") denied("agent_type");
462
+ const origin = receivedAgentOrigin(member[0]);
463
+ const { created, expires, nonce, keyid, alg } = input.params;
464
+ if ([created, expires, nonce, keyid, alg].some((value) => value === void 0)) denied("parameters_missing");
465
+ const nonceKey = nonceBytes(nonce)?.toString("base64url");
466
+ if (!nonceKey || !Number.isSafeInteger(created) || !Number.isSafeInteger(expires) || typeof keyid !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(keyid) || !WEB_BOT_AUTH_ALGORITHMS.includes(alg))
467
+ denied("parameters");
468
+ if (expires <= created || expires - created > MAX_LIFETIME_SECONDS || created > now + CLOCK_SKEW_SECONDS || expires < now - CLOCK_SKEW_SECONDS)
469
+ denied("window");
470
+ if (new Set(input.components).size !== input.components.length) denied("coverage");
471
+ const required = [(0, import_http_message_sig.component)("@method"), (0, import_http_message_sig.component)("@target-uri"), (0, import_http_message_sig.component)("@authority")];
472
+ if (hasBody) required.push((0, import_http_message_sig.component)("content-type"), (0, import_http_message_sig.component)("content-digest"));
473
+ required.push((0, import_http_message_sig.component)("signature-agent", { key: label }));
474
+ const covered = new Set(input.components);
475
+ if (required.some((c) => !covered.has((0, import_http_message_sig.componentIdentity)(c).replace(/^"([^"]*)"/, "$1")))) denied("coverage");
476
+ if (hasBody) assertDigest(request.headers, request.body);
477
+ let components;
478
+ try {
479
+ components = input.components.map(componentDescriptor);
480
+ } catch {
481
+ return denied("component");
466
482
  }
467
- for (const signer of verified) verifiedSigners.add(signer);
468
- return verified;
469
- } catch (error) {
470
- if (error instanceof WebBotAuthError) {
471
- if (error.headers["Accept-Signature"]) {
472
- let label = "sig1";
473
- try {
474
- label = inputs(request.headers)[0]?.label ?? "sig1";
475
- } catch {
476
- }
477
- const bodyComponents = request.body ? '"content-type" "content-digest" ' : "";
478
- error.headers["Accept-Signature"] = `${label}=("@method" "@target-uri" "@authority" ${bodyComponents}"signature-agent";key="${label}");created;expires;nonce;keyid;tag="web-bot-auth"`;
479
- }
480
- throw error;
483
+ const fields = [
484
+ ...coveredFields(components, request.headers),
485
+ { name: "signature-input", value: inputHeader },
486
+ { name: "signature", value: signatureHeader }
487
+ ];
488
+ await verifyWithLibrary(
489
+ {
490
+ kind: "request",
491
+ method: request.method.toUpperCase(),
492
+ targetUri: receivedTargetUri(request.url),
493
+ fields
494
+ },
495
+ label,
496
+ { requiredComponents: required, requiredParameters: REQUIRED_PARAMETERS, maxAge: MAX_LIFETIME_SECONDS, now },
497
+ async () => keyVerifier(await directoryKey(options, origin, keyid, now))
498
+ );
499
+ let result;
500
+ try {
501
+ result = await options.replayStore.insert(
502
+ origin,
503
+ WEB_BOT_AUTH_PROFILE,
504
+ nonceKey,
505
+ // Acceptance is inclusive of expires + skew, so the nonce must outlive it.
506
+ expires + CLOCK_SKEW_SECONDS - now + 1,
507
+ now
508
+ );
509
+ } catch (cause) {
510
+ throw new WebBotAuthUnavailableError("replay_unavailable", cause);
481
511
  }
512
+ if (result === "replayed") throw new WebBotAuthError(429, "replayed");
513
+ if (result !== "ok") throw new WebBotAuthError(429, result);
514
+ if (options.isRevoked) await legacyRevocationCheck(options, origin, keyid);
515
+ const signer = Object.freeze({
516
+ profile: WEB_BOT_AUTH_PROFILE,
517
+ origin,
518
+ keyid,
519
+ label,
520
+ verified_at: now
521
+ });
522
+ verifiedSigners.add(signer);
523
+ return [signer];
524
+ } catch (error) {
525
+ if (error instanceof WebBotAuthError) throw challengeFor(error, label, hasBody);
482
526
  if (error instanceof WebBotAuthUnavailableError) throw error;
483
- return denied("verification_unavailable");
527
+ throw challengeFor(new WebBotAuthError(403, "verification_unavailable"), label, hasBody);
484
528
  }
485
529
  }
530
+ async function directoryKey(options, origin, keyid, now) {
531
+ await options.directory.resolve(keyid, origin);
532
+ const checkedDirectory = options.directory.getVerifiedDirectory(origin);
533
+ if (!checkedDirectory || new URL(checkedDirectory.proof.request.url).origin !== origin) denied("directory_origin");
534
+ if (checkedDirectory.expires <= now) denied("directory_expired");
535
+ if (!testKeysAllowed(options.allowTestKeys ?? options.directory.allowsTestKeys) && checkedDirectory.keys.some((key2) => TEST_KIDS.has(key2.kid)))
536
+ denied("directory_key");
537
+ const key = checkedDirectory.keys.find((key2) => key2.kid === keyid);
538
+ if (!key || jwkThumbprint(key) !== keyid) denied("key");
539
+ return key;
540
+ }
486
541
  async function legacyRevocationCheck(options, origin, keyid) {
487
542
  let revoked;
488
543
  try {
@@ -501,29 +556,27 @@ async function legacyRevocationCheck(options, origin, keyid) {
501
556
  function testKeysAllowed(allowTestKeys) {
502
557
  return allowTestKeys === true && ["test", "development"].includes(process.env.NODE_ENV ?? "");
503
558
  }
504
- const UNSIGNED_DIRECTORY_MEDIA_TYPES = /* @__PURE__ */ new Set([
505
- SIGNATURE_DIRECTORY_MEDIA_TYPE,
506
- "application/jwk-set+json",
507
- "application/json"
508
- ]);
509
- function verifyKeyDirectory(response, now = Date.now() / 1e3, options = {}) {
559
+ async function verifyKeyDirectory(response, now = Date.now() / 1e3, options = {}) {
510
560
  try {
511
561
  if (!Number.isFinite(now)) denied("directory_invalid");
512
562
  const origin = (0, import_canonicalize.canonicalAgentOrigin)(response.request.url);
513
563
  if (response.request.url !== origin + SIGNATURE_DIRECTORY_PATH || response.status !== 200)
514
564
  denied("directory_response");
515
- const signed = (0, import_canonicalize2.getHeaderValue)(response.headers, "signature-input") !== void 0;
516
- if (!signed && options.requireSigned) denied("directory_unsigned");
565
+ const inputHeader = (0, import_canonicalize2.getHeaderValue)(response.headers, "signature-input");
566
+ const signatureHeader = (0, import_canonicalize2.getHeaderValue)(response.headers, "signature");
567
+ if (!inputHeader || !signatureHeader) denied("directory_unsigned");
517
568
  const mediaType = (0, import_canonicalize2.getHeaderValue)(response.headers, "content-type")?.split(";")[0]?.trim().toLowerCase();
518
- if (signed ? mediaType !== SIGNATURE_DIRECTORY_MEDIA_TYPE : !UNSIGNED_DIRECTORY_MEDIA_TYPES.has(mediaType ?? ""))
519
- denied("directory_response");
569
+ if (mediaType !== SIGNATURE_DIRECTORY_MEDIA_TYPE) denied("directory_response");
520
570
  const body = response.body ?? "";
521
571
  if (Buffer.byteLength(body) > 256 * 1024) denied("directory_size");
522
- if (signed || (0, import_canonicalize2.getHeaderValue)(response.headers, "content-digest") !== void 0)
523
- assertDirectoryDigest(response.headers, body);
572
+ try {
573
+ assertDigest(response.headers, body);
574
+ } catch {
575
+ denied("directory_digest");
576
+ }
524
577
  const document = (0, import_strict_json.parseStrictJson)(body, 32);
525
578
  if (!Array.isArray(document.keys) || !document.keys.length || document.keys.length > 64) denied("directory_keys");
526
- const signatures = signed ? inputs(response.headers) : [];
579
+ const signatures = inputs(response.headers, 64);
527
580
  const kids = /* @__PURE__ */ new Set();
528
581
  const keys = [];
529
582
  let expires = Infinity;
@@ -536,15 +589,47 @@ function verifyKeyDirectory(response, now = Date.now() / 1e3, options = {}) {
536
589
  algorithm(key);
537
590
  kids.add(key.kid);
538
591
  keys.push(key);
539
- if (!signed) continue;
540
592
  const proof = signatures.find((input) => input.params.keyid === key.kid && input.params.tag === DIRECTORY_TAG);
541
- if (!proof || !proof.components.includes("@authority;req") || !proof.components.includes("content-digest") || !Number.isInteger(proof.params.created) || !Number.isInteger(proof.params.expires) || proof.params.created > now + DIRECTORY_CLOCK_SKEW_SECONDS || proof.params.expires <= now || proof.params.expires <= proof.params.created)
593
+ if (!proof || !proof.components.includes("@authority;req") || !proof.components.includes("content-digest") || !Number.isSafeInteger(proof.params.created) || !Number.isSafeInteger(proof.params.expires) || proof.params.expires <= now || proof.params.expires <= proof.params.created)
542
594
  denied("directory_proof");
543
- verifyBytes(proof, { ...response.request, headers: response.request.headers ?? {} }, key, response);
595
+ const proofComponents = [(0, import_http_message_sig.component)("@authority", { req: true }), (0, import_http_message_sig.component)("content-digest")];
596
+ let covered;
597
+ try {
598
+ covered = proof.components.map(componentDescriptor);
599
+ } catch {
600
+ return denied("directory_proof");
601
+ }
602
+ const requestHeaders = response.request.headers ?? {};
603
+ await verifyWithLibrary(
604
+ {
605
+ kind: "response",
606
+ status: response.status,
607
+ fields: [
608
+ ...coveredFields(
609
+ covered.filter((c) => c.parameters.req === void 0),
610
+ response.headers
611
+ ),
612
+ { name: "signature-input", value: inputHeader },
613
+ { name: "signature", value: signatureHeader }
614
+ ],
615
+ request: {
616
+ kind: "request",
617
+ method: response.request.method,
618
+ targetUri: response.request.url,
619
+ fields: coveredFields(
620
+ covered.filter((c) => c.parameters.req !== void 0),
621
+ requestHeaders
622
+ )
623
+ }
624
+ },
625
+ proof.label,
626
+ // The library rejects a proof whose `created` is in the future (beyond skew).
627
+ { requiredComponents: proofComponents, requiredParameters: ["created", "expires", "keyid", "tag"], now },
628
+ async () => keyVerifier(key)
629
+ );
544
630
  expires = Math.min(expires, proof.params.expires);
545
631
  }
546
- if (!signed) expires = now + unsignedAge(options.maxUnsignedAgeSeconds);
547
- return { keys, expires, signed, proof: response };
632
+ return { keys, expires, signed: true, proof: response };
548
633
  } catch (error) {
549
634
  if (error instanceof WebBotAuthError) {
550
635
  throw error.reason.startsWith("directory_") ? error : new WebBotAuthError(403, `directory_${error.reason}`);
@@ -552,26 +637,12 @@ function verifyKeyDirectory(response, now = Date.now() / 1e3, options = {}) {
552
637
  return denied("directory_invalid");
553
638
  }
554
639
  }
555
- function unsignedAge(seconds) {
556
- const value = seconds ?? 86400;
557
- if (!Number.isFinite(value) || value < 60)
558
- throw new TypeError("Unsigned directory age must be a finite number of seconds, at least 60");
559
- return value;
560
- }
561
640
  function verifySignatureDirectory(response, now = Date.now() / 1e3, allowTestKeys = false) {
562
- return verifyKeyDirectory(response, now, { allowTestKeys, requireSigned: true });
563
- }
564
- function assertDirectoryDigest(headers, body) {
565
- try {
566
- assertDigest(headers, body);
567
- } catch {
568
- denied("directory_digest");
569
- }
641
+ return verifyKeyDirectory(response, now, { allowTestKeys });
570
642
  }
571
643
  class SignatureDirectoryResolver {
572
644
  constructor(options = {}) {
573
645
  this.options = options;
574
- unsignedAge(options.maxUnsignedDirectoryAgeSeconds);
575
646
  }
576
647
  options;
577
648
  cache = /* @__PURE__ */ new Map();
@@ -618,22 +689,16 @@ class SignatureDirectoryResolver {
618
689
  const url = origin + SIGNATURE_DIRECTORY_PATH;
619
690
  const result = await (0, import_net.ssrfSafeFetch)(url, {
620
691
  ...this.options.fetchOptions,
621
- headers: {
622
- accept: `${SIGNATURE_DIRECTORY_MEDIA_TYPE}, application/jwk-set+json;q=0.9, application/json;q=0.8`
623
- },
692
+ headers: { accept: SIGNATURE_DIRECTORY_MEDIA_TYPE },
624
693
  timeoutMs: 1e4,
625
694
  maxBodyBytes: 256 * 1024
626
695
  });
627
696
  if (result.status !== 200) denied("directory_unavailable");
628
697
  const body = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(result.body);
629
- const directory = verifyKeyDirectory(
698
+ const directory = await verifyKeyDirectory(
630
699
  { status: result.status, headers: result.headers, body, request: { method: "GET", url } },
631
700
  now,
632
- {
633
- allowTestKeys: this.options.allowTestKeys,
634
- requireSigned: this.options.requireSignedDirectories,
635
- maxUnsignedAgeSeconds: this.options.maxUnsignedDirectoryAgeSeconds
636
- }
701
+ { allowTestKeys: this.options.allowTestKeys }
637
702
  );
638
703
  const cacheControl = result.headers["cache-control"] ?? "";
639
704
  const maxAge = /(?:^|,)\s*(?:no-cache|no-store)(?:,|$)/i.test(cacheControl) ? 0 : Number(cacheControl.match(/(?:^|,)\s*max-age=(\d+)/i)?.[1] ?? 300);
@@ -670,7 +735,7 @@ class SignatureDirectoryResolver {
670
735
  }
671
736
  function webBotSigner(key) {
672
737
  const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
673
- const alg = algorithm(jwk);
738
+ const alg = signingAlgorithm(jwk);
674
739
  if (!["test", "development"].includes(process.env.NODE_ENV ?? "") && TEST_KIDS.has(jwkThumbprint(jwk)))
675
740
  throw new TypeError("Published Web Bot Auth test keys cannot sign in production");
676
741
  if ("provider" in key) {
@@ -678,17 +743,6 @@ function webBotSigner(key) {
678
743
  throw new TypeError("Web Bot Auth provider identity does not match its public key");
679
744
  return { algorithm: alg, sign: async (data) => Buffer.from(await key.provider.sign(Buffer.from(data))) };
680
745
  }
681
- if (alg === "rsa-pss-sha512") {
682
- const privateKey = (0, import_crypto.createPrivateKey)({ key: jwk, format: "jwk" });
683
- return {
684
- algorithm: alg,
685
- sign: (data) => (0, import_crypto.sign)("sha512", Buffer.from(data), {
686
- key: privateKey,
687
- padding: import_crypto.constants.RSA_PKCS1_PSS_PADDING,
688
- saltLength: 64
689
- })
690
- };
691
- }
692
746
  return { algorithm: alg, sign: (data) => (0, import_crypto2.signWithPrivateJwkAsync)(alg, jwk, Buffer.from(data)) };
693
747
  }
694
748
  async function createWebBotSignature(...args) {
@@ -699,38 +753,28 @@ async function createWebBotSignature(...args) {
699
753
  throw error;
700
754
  }
701
755
  }
702
- function componentDescriptor(identifier) {
703
- const bare = identifier.split(";", 1)[0];
704
- const [, params] = (0, import_structured_headers.parseItem)(`"${bare}"${identifier.slice(bare.length)}`);
705
- return (0, import_http_message_sig.component)(bare, Object.fromEntries(params));
706
- }
707
- function coveredFields(components, headers) {
708
- const names = new Set(components.filter((c) => !c.name.startsWith("@")).map((c) => c.name));
709
- return [...names].flatMap((name) => {
710
- const value = (0, import_canonicalize2.getHeaderValue)(headers, name);
711
- return value === void 0 ? [] : [{ name, value }];
712
- });
713
- }
714
756
  async function signWebBotAuth(request, key, options) {
757
+ if (options.relay)
758
+ throw new TypeError(
759
+ "Web Bot Auth relays were removed from the profile (adcp#7894): an intermediary signs as itself with one signature"
760
+ );
715
761
  const origin = (0, import_canonicalize.canonicalAgentOrigin)(options.origin);
716
762
  const label = options.label ?? "sig1";
717
763
  if (!/^[a-z][a-z0-9_.*-]*$/.test(label)) throw new TypeError("Invalid signature label");
718
764
  const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
719
765
  const now = Math.floor((options.now ?? (() => Date.now() / 1e3))());
720
- const window = options.windowSeconds ?? 300;
721
- const nonce = options.nonce ?? (0, import_crypto.randomBytes)(64).toString("base64url");
766
+ const window = options.windowSeconds ?? MAX_LIFETIME_SECONDS;
767
+ const nonce = options.nonce ?? freshNonce();
722
768
  if (!Number.isSafeInteger(now)) throw new TypeError("Invalid Web Bot Auth clock");
723
- if (!Number.isInteger(window) || window < 1 || window > 300 || !/^[A-Za-z0-9_+\/-]{22,256}={0,2}$/.test(nonce))
724
- throw new TypeError("Invalid Web Bot Auth lifetime or nonce");
725
- if (!options.relay && ["signature", "signature-input", "signature-agent"].some(
769
+ if (!Number.isInteger(window) || window < 1 || window > MAX_LIFETIME_SECONDS || !nonceBytes(nonce))
770
+ throw new TypeError("Invalid Web Bot Auth lifetime or nonce (the nonce is 64 bytes, Base64URL or Base64)");
771
+ if (["signature", "signature-input", "signature-agent"].some(
726
772
  (name) => (0, import_canonicalize2.getHeaderValue)(request.headers, name) !== void 0
727
773
  ))
728
- throw new TypeError(
729
- "Request already carries signature fields; pass relay: true to add a covering signature instead of replacing them"
730
- );
774
+ throw new TypeError("Request already carries signature fields; the profile takes exactly one signature");
731
775
  const headers = {};
732
776
  for (const [name, value] of Object.entries(request.headers))
733
- if (value !== void 0 && !["signature", "signature-input", "signature-agent", "content-digest"].includes(name.toLowerCase()))
777
+ if (value !== void 0 && name.toLowerCase() !== "content-digest")
734
778
  headers[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
735
779
  const components = [(0, import_http_message_sig.component)("@method"), (0, import_http_message_sig.component)("@target-uri"), (0, import_http_message_sig.component)("@authority")];
736
780
  if (request.body !== void 0 && request.body !== "") {
@@ -739,37 +783,8 @@ async function signWebBotAuth(request, key, options) {
739
783
  headers["content-digest"] = (0, import_content_digest.computeContentDigest)(request.body, "rfc8941-base64");
740
784
  components.push((0, import_http_message_sig.component)("content-type"), (0, import_http_message_sig.component)("content-digest"));
741
785
  }
742
- if (options.relay && ["signature", "signature-input", "signature-agent"].some((name) => !(0, import_canonicalize2.getHeaderValue)(request.headers, name)))
743
- throw new TypeError("Relay requires the existing signature fields");
744
- const earlier = options.relay ? inputs(request.headers) : [];
745
- if (earlier.some((input) => input.label === label || input.params.tag !== WEB_BOT_AUTH_PROFILE))
746
- throw new TypeError("Relay requires existing Web Bot Auth signatures and a fresh label");
747
- const priorAgent = options.relay ? (0, import_canonicalize2.getHeaderValue)(request.headers, "signature-agent") : void 0;
748
- if (priorAgent) dictionary(priorAgent, "Signature-Agent");
749
- headers["signature-agent"] = `${priorAgent ? priorAgent + ", " : ""}${label}="${origin}"`;
750
- if (priorAgent) dictionary(headers["signature-agent"], "Signature-Agent");
786
+ headers["signature-agent"] = `${label}="${origin}"`;
751
787
  components.push((0, import_http_message_sig.component)("signature-agent", { key: label }));
752
- const covered = new Set(components.map(import_http_message_sig.componentIdentity));
753
- const cover = (descriptor) => {
754
- const identity = (0, import_http_message_sig.componentIdentity)(descriptor);
755
- if (!covered.has(identity)) {
756
- covered.add(identity);
757
- components.push(descriptor);
758
- }
759
- };
760
- for (const inner of earlier) {
761
- for (const identifier of inner.components) cover(componentDescriptor(identifier));
762
- cover((0, import_http_message_sig.component)("signature-input", { key: inner.label }));
763
- cover((0, import_http_message_sig.component)("signature", { key: inner.label }));
764
- }
765
- const priorInput = options.relay ? (0, import_canonicalize2.getHeaderValue)(request.headers, "signature-input") : void 0;
766
- const priorSignature = options.relay ? (0, import_canonicalize2.getHeaderValue)(request.headers, "signature") : void 0;
767
- if (options.relay && (!priorInput || !priorSignature || !priorAgent))
768
- throw new TypeError("Relay requires the existing signature fields");
769
- if (priorInput) headers["signature-input"] = priorInput;
770
- if (priorSignature) headers.signature = priorSignature;
771
- for (const inner of earlier)
772
- buildWebBotAuthSignatureBase(inner, { ...request, url: new URL(request.url).href, headers });
773
788
  const targetUri = new URL(request.url);
774
789
  targetUri.hash = "";
775
790
  const signed = await createWebBotSignature(
@@ -787,14 +802,14 @@ async function signWebBotAuth(request, key, options) {
787
802
  expires: now + window,
788
803
  nonce,
789
804
  keyid: jwkThumbprint(jwk),
790
- alg: algorithm(jwk),
805
+ alg: signingAlgorithm(jwk),
791
806
  tag: WEB_BOT_AUTH_PROFILE
792
807
  },
793
808
  signer: webBotSigner(key)
794
809
  }
795
810
  );
796
- headers["signature-input"] = `${priorInput ? priorInput + ", " : ""}${signed.signatureInput}`;
797
- headers.signature = `${priorSignature ? priorSignature + ", " : ""}${signed.signature}`;
811
+ headers["signature-input"] = signed.signatureInput;
812
+ headers.signature = signed.signature;
798
813
  return headers;
799
814
  }
800
815
  async function createSignedSignatureDirectory(originUrl, signingKeys, options = {}) {
@@ -803,14 +818,13 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
803
818
  const lifetime = options.lifetimeSeconds ?? 86400;
804
819
  if (!Number.isFinite(now) || !Number.isSafeInteger(lifetime) || lifetime < 1 || !Number.isSafeInteger(now + lifetime))
805
820
  throw new TypeError("Invalid directory proof clock or lifetime");
806
- if (!signingKeys.length || signingKeys.length > 64 || !Number.isInteger(lifetime) || lifetime < 1)
807
- throw new TypeError("Invalid directory keys or lifetime");
821
+ if (!signingKeys.length || signingKeys.length > 64) throw new TypeError("Invalid directory keys or lifetime");
808
822
  const keys = signingKeys.map((key) => {
809
823
  const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
824
+ signingAlgorithm(jwk);
810
825
  return {
811
826
  kty: jwk.kty,
812
827
  ...jwk.kty === "RSA" ? { n: jwk.n, e: jwk.e } : { crv: jwk.crv, x: jwk.x },
813
- ...jwk.y ? { y: jwk.y } : {},
814
828
  kid: jwkThumbprint(jwk),
815
829
  use: "sig"
816
830
  };
@@ -841,10 +855,10 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
841
855
  components: [(0, import_http_message_sig.component)("@authority", { req: true }), (0, import_http_message_sig.component)("content-digest")],
842
856
  parameters: {
843
857
  // Backdate created so verifiers whose clocks run slightly behind accept a fresh proof.
844
- created: now - DIRECTORY_CLOCK_SKEW_SECONDS,
858
+ created: now - CLOCK_SKEW_SECONDS,
845
859
  expires: now + lifetime,
846
860
  keyid: keys[i].kid,
847
- alg: algorithm(keys[i]),
861
+ alg: signingAlgorithm(keys[i]),
848
862
  tag: DIRECTORY_TAG
849
863
  },
850
864
  signer: webBotSigner(signingKeys[i])
@@ -858,6 +872,7 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
858
872
  return response;
859
873
  }
860
874
  function createWebBotAuthFetch(upstream, key, options) {
875
+ assertWebBotAuthSigningKey(key);
861
876
  return async (input, init) => {
862
877
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
863
878
  if (options.shouldSign && !options.shouldSign(url, init)) return upstream(input, init);
@@ -888,10 +903,13 @@ function createWebBotAuthFetch(upstream, key, options) {
888
903
  SIGNATURE_DIRECTORY_MEDIA_TYPE,
889
904
  SIGNATURE_DIRECTORY_PATH,
890
905
  SignatureDirectoryResolver,
906
+ WEB_BOT_AUTH_ALGORITHMS,
891
907
  WEB_BOT_AUTH_DRAFT_REVISION,
892
908
  WEB_BOT_AUTH_PROFILE,
893
909
  WebBotAuthError,
894
910
  WebBotAuthUnavailableError,
911
+ advertisesOnlyWebBotAuth,
912
+ assertWebBotAuthSigningKey,
895
913
  buildWebBotAuthSignatureBase,
896
914
  createSignedSignatureDirectory,
897
915
  createWebBotAuthAuthorizationFailure,
@@ -907,9 +925,11 @@ function createWebBotAuthFetch(upstream, key, options) {
907
925
  registerWebBotAuthRevocationCheck,
908
926
  selectSignatureProfile,
909
927
  signWebBotAuth,
928
+ unsignedWebBotAuthError,
910
929
  verifyKeyDirectory,
911
930
  verifySignatureDirectory,
912
931
  verifyWebBotAuth,
932
+ webBotAuthAcceptSignature,
913
933
  webBotAuthPrincipalFailureError,
914
934
  webBotAuthToolFailure,
915
935
  webBotAuthUnavailableResponse