@adcp/sdk 16.0.0-beta.9 → 16.0.0

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 (34) hide show
  1. package/README.md +2 -2
  2. package/dist/lib/identity-3.3/revocation.js +6 -7
  3. package/dist/lib/identity-3.3/revocation.mjs +6 -7
  4. package/dist/lib/server/create-adcp-server.js +1 -1
  5. package/dist/lib/server/create-adcp-server.mjs +1 -1
  6. package/dist/lib/signing/crypto.d.mts +1 -1
  7. package/dist/lib/signing/crypto.d.ts +1 -1
  8. package/dist/lib/signing/crypto.js +2 -45
  9. package/dist/lib/signing/crypto.mjs +2 -46
  10. package/dist/lib/signing/web-bot-auth.d.mts +18 -10
  11. package/dist/lib/signing/web-bot-auth.d.ts +18 -10
  12. package/dist/lib/signing/web-bot-auth.js +29 -24
  13. package/dist/lib/signing/web-bot-auth.mjs +29 -24
  14. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  15. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  16. package/dist/lib/v2/projection/catalog.d.mts +4 -0
  17. package/dist/lib/v2/projection/catalog.d.ts +4 -0
  18. package/dist/lib/v2/projection/creative-delivery.d.mts +15 -9
  19. package/dist/lib/v2/projection/creative-delivery.d.ts +15 -9
  20. package/dist/lib/v2/projection/creative-delivery.js +71 -10
  21. package/dist/lib/v2/projection/creative-delivery.mjs +71 -10
  22. package/dist/lib/version.d.mts +3 -3
  23. package/dist/lib/version.d.ts +3 -3
  24. package/dist/lib/version.js +3 -3
  25. package/dist/lib/version.mjs +3 -3
  26. package/docs/TYPE-SUMMARY.md +1 -1
  27. package/docs/guides/CREATIVE-DELIVERY.md +18 -1
  28. package/docs/guides/REPORTING-STABILITY.md +7 -7
  29. package/docs/llms.txt +2 -2
  30. package/docs/migration-14.x-rc-worksheet.md +4 -4
  31. package/docs/migration-agent-identity-3.3.md +44 -19
  32. package/docs/migration-reporting-storage-package.md +3 -3
  33. package/docs/releases/16.0.0.md +20 -0
  34. package/package.json +1 -1
package/README.md CHANGED
@@ -29,14 +29,14 @@ AdCP operations are **distributed and asynchronous by default**. An agent might:
29
29
  ## Installation
30
30
 
31
31
  ```bash
32
- npm install @adcp/sdk # 14.x, AdCP 3.2
32
+ npm install @adcp/sdk # 16.x, AdCP 3.2
33
33
  npm install @adcp/sdk@adcp-3.1 # 13.x, maintained AdCP 3.1 line
34
34
  npm install @adcp/sdk@adcp-3.0 # 7.11.x, AdCP 3.0
35
35
  ```
36
36
 
37
37
  AdCP 2 reached end of life on August 1, 2026. This checkout supports AdCP 3 agents only; review the [AdCP 2 retirement guide](./docs/migration-drop-adcp-2.md) before upgrading an integration that still calls AdCP 2 sellers.
38
38
 
39
- Upgrading to SDK 14? Read the [release-bound upgrade worksheet](./docs/migration-14.x-rc-worksheet.md), then use the [13-to-14](./docs/migration-13-to-14.md) or [12-to-14](./docs/migration-12-to-14.md) migration guide. The [prerelease history](./docs/releases/14.0.0-beta.0.md) covers changes made during the 14.0 beta cycle. The npm `latest` tag tracks SDK 14 (AdCP 3.2); SDK 13 stays available on `adcp-3.1` as the maintained AdCP 3.1 line. SDK 14 requires Node.js `^20.19.0 || >=22.12.0`. Older paths: [12-to-13](./docs/migration-12-to-13.md), **[MIGRATION-v8.md](./MIGRATION-v8.md)**, and [8.0-to-8.1](./docs/migration-8.0-to-8.1.md).
39
+ Upgrading to SDK 16? Read the [SDK 16 release notes](./docs/releases/16.0.0.md) and [release-bound upgrade worksheet](./docs/migration-14.x-rc-worksheet.md). The npm `latest` tag tracks SDK 16 (AdCP 3.2); SDK 13 stays available on `adcp-3.1` as the maintained AdCP 3.1 line. SDK 16 requires Node.js `^20.19.0 || >=22.12.0`. Earlier migration guides: [SDK 15 OAuth](./docs/migration-oauth-issuer-bindings.md), [13-to-14](./docs/migration-13-to-14.md), [12-to-14](./docs/migration-12-to-14.md), [12-to-13](./docs/migration-12-to-13.md), **[MIGRATION-v8.md](./MIGRATION-v8.md)**, and [8.0-to-8.1](./docs/migration-8.0-to-8.1.md).
40
40
 
41
41
  ### Narrow type imports (`@adcp/sdk/types/<tool>`)
42
42
 
@@ -225,17 +225,15 @@ class OriginRevocationResolver {
225
225
  for (const token of tokens) {
226
226
  try {
227
227
  const header = (0, import_jose.decodeProtectedHeader)(token);
228
- if (header.typ !== "adcp-gov-revocation+jws" || typeof header.kid !== "string" || // Directory keys are Ed25519 or RSA-PSS under the profile (adcp#7894).
229
- header.alg !== "EdDSA" && header.alg !== "PS512")
230
- continue;
228
+ if (header.typ !== "adcp-gov-revocation+jws" || typeof header.kid !== "string") continue;
231
229
  await this.options.directory.resolve(header.kid, origin);
232
230
  const checked = this.options.directory.getVerifiedDirectory(origin);
233
231
  if (!checked || new URL(checked.proof.request.url).origin !== origin || checked.expires <= this.now())
234
232
  continue;
235
233
  const jwk = checked.keys.find((key) => key.kid === header.kid);
236
234
  if (!jwk) continue;
237
- const result = await (0, import_jose.compactVerify)(token, await (0, import_jose.importJWK)(jwk, header.alg), {
238
- algorithms: [header.alg]
235
+ const result = await (0, import_jose.compactVerify)(token, await (0, import_jose.importJWK)(jwk, "EdDSA"), {
236
+ algorithms: ["EdDSA"]
239
237
  });
240
238
  verifiedPayload = result.payload;
241
239
  break;
@@ -296,8 +294,9 @@ async function createSignedOriginRevocationList(snapshot, privateKey) {
296
294
  const kid = (0, import_web_bot_auth.jwkThumbprint)(privateKey);
297
295
  if (privateKey.kid && privateKey.kid !== kid)
298
296
  throw new TypeError("Revocation signing kid must be the JWK thumbprint");
299
- const alg = privateKey.crv === "Ed25519" ? "EdDSA" : privateKey.kty === "RSA" ? "PS512" : void 0;
300
- if (!alg || !privateKey.d) throw new TypeError("Revocations require a private Ed25519 or RSA key");
297
+ const alg = "EdDSA";
298
+ if (privateKey.kty !== "OKP" || privateKey.crv !== "Ed25519" || !privateKey.d)
299
+ throw new TypeError("Revocations require a private Ed25519 key");
301
300
  return new import_jose.CompactSign(Buffer.from(JSON.stringify(snapshot))).setProtectedHeader({ alg, kid, typ: "adcp-gov-revocation+jws" }).sign(await (0, import_jose.importJWK)(privateKey, alg));
302
301
  }
303
302
  // Annotate the CommonJS export names for ESM import in node:
@@ -204,17 +204,15 @@ class OriginRevocationResolver {
204
204
  for (const token of tokens) {
205
205
  try {
206
206
  const header = decodeProtectedHeader(token);
207
- if (header.typ !== "adcp-gov-revocation+jws" || typeof header.kid !== "string" || // Directory keys are Ed25519 or RSA-PSS under the profile (adcp#7894).
208
- header.alg !== "EdDSA" && header.alg !== "PS512")
209
- continue;
207
+ if (header.typ !== "adcp-gov-revocation+jws" || typeof header.kid !== "string") continue;
210
208
  await this.options.directory.resolve(header.kid, origin);
211
209
  const checked = this.options.directory.getVerifiedDirectory(origin);
212
210
  if (!checked || new URL(checked.proof.request.url).origin !== origin || checked.expires <= this.now())
213
211
  continue;
214
212
  const jwk = checked.keys.find((key) => key.kid === header.kid);
215
213
  if (!jwk) continue;
216
- const result = await compactVerify(token, await importJWK(jwk, header.alg), {
217
- algorithms: [header.alg]
214
+ const result = await compactVerify(token, await importJWK(jwk, "EdDSA"), {
215
+ algorithms: ["EdDSA"]
218
216
  });
219
217
  verifiedPayload = result.payload;
220
218
  break;
@@ -275,8 +273,9 @@ async function createSignedOriginRevocationList(snapshot, privateKey) {
275
273
  const kid = jwkThumbprint(privateKey);
276
274
  if (privateKey.kid && privateKey.kid !== kid)
277
275
  throw new TypeError("Revocation signing kid must be the JWK thumbprint");
278
- const alg = privateKey.crv === "Ed25519" ? "EdDSA" : privateKey.kty === "RSA" ? "PS512" : void 0;
279
- if (!alg || !privateKey.d) throw new TypeError("Revocations require a private Ed25519 or RSA key");
276
+ const alg = "EdDSA";
277
+ if (privateKey.kty !== "OKP" || privateKey.crv !== "Ed25519" || !privateKey.d)
278
+ throw new TypeError("Revocations require a private Ed25519 key");
280
279
  return new CompactSign(Buffer.from(JSON.stringify(snapshot))).setProtectedHeader({ alg, kid, typ: "adcp-gov-revocation+jws" }).sign(await importJWK(privateKey, alg));
281
280
  }
282
281
  export {
@@ -4581,7 +4581,7 @@ function createAdcpServer(config) {
4581
4581
  .../* @__PURE__ */ new Set([
4582
4582
  algorithm,
4583
4583
  // `algorithms` lists AdCP webhook-profile values; a Web Bot Auth key
4584
- // is Ed25519 or RSA-PSS (adcp#7894), and only Ed25519 has a value here.
4584
+ // is Ed25519 (adcp#7894), which has the value `ed25519` here.
4585
4585
  ...webhooks.webBotAuth && ("privateKey" in webhooks.webBotAuth.key ? webhooks.webBotAuth.key.privateKey.crv : webhooks.webBotAuth.key.publicKey.crv) === "Ed25519" ? ["ed25519"] : []
4586
4586
  ])
4587
4587
  ],
@@ -4672,7 +4672,7 @@ function createAdcpServer(config) {
4672
4672
  .../* @__PURE__ */ new Set([
4673
4673
  algorithm,
4674
4674
  // `algorithms` lists AdCP webhook-profile values; a Web Bot Auth key
4675
- // is Ed25519 or RSA-PSS (adcp#7894), and only Ed25519 has a value here.
4675
+ // is Ed25519 (adcp#7894), which has the value `ed25519` here.
4676
4676
  ...webhooks.webBotAuth && ("privateKey" in webhooks.webBotAuth.key ? webhooks.webBotAuth.key.privateKey.crv : webhooks.webBotAuth.key.publicKey.crv) === "Ed25519" ? ["ed25519"] : []
4677
4677
  ])
4678
4678
  ],
@@ -15,5 +15,5 @@ export declare function verifySignatureAsync(alg: string, publicKey: KeyObject |
15
15
  * path passes the key as a JWK so workerd accepts it (#3243).
16
16
  */
17
17
  export declare function signWithPrivateJwk(alg: string, privateJwk: AdcpJsonWebKey, data: Uint8Array): Uint8Array;
18
- /** {@link signWithPrivateJwk}, preferring WebCrypto for ES256 and RSA-PSS when available (workerd, #3243). */
18
+ /** {@link signWithPrivateJwk}, preferring WebCrypto for ES256 when available (workerd, #3243). */
19
19
  export declare function signWithPrivateJwkAsync(alg: string, privateJwk: AdcpJsonWebKey, data: Uint8Array): Promise<Uint8Array>;
@@ -15,5 +15,5 @@ export declare function verifySignatureAsync(alg: string, publicKey: KeyObject |
15
15
  * path passes the key as a JWK so workerd accepts it (#3243).
16
16
  */
17
17
  export declare function signWithPrivateJwk(alg: string, privateJwk: AdcpJsonWebKey, data: Uint8Array): Uint8Array;
18
- /** {@link signWithPrivateJwk}, preferring WebCrypto for ES256 and RSA-PSS when available (workerd, #3243). */
18
+ /** {@link signWithPrivateJwk}, preferring WebCrypto for ES256 when available (workerd, #3243). */
19
19
  export declare function signWithPrivateJwkAsync(alg: string, privateJwk: AdcpJsonWebKey, data: Uint8Array): Promise<Uint8Array>;
@@ -59,14 +59,6 @@ function verifySignature(alg, publicKey, data, signature) {
59
59
  );
60
60
  }
61
61
  }
62
- if (alg === "rsa-pss-sha512") {
63
- return (0, import_crypto.verify)(
64
- "sha512",
65
- data,
66
- { key: publicKey, padding: import_crypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: 64 },
67
- signature
68
- );
69
- }
70
62
  return false;
71
63
  } catch {
72
64
  return false;
@@ -87,36 +79,15 @@ function privateP256Jwk(jwk) {
87
79
  async function verifySignatureAsync(alg, publicKey, data, signature) {
88
80
  const subtle = subtleCrypto();
89
81
  const keyObject = () => isKeyObject(publicKey) ? publicKey : jwkToPublicKey(publicKey);
90
- if (alg !== "ecdsa-p256-sha256" && alg !== "rsa-pss-sha512" || !subtle)
91
- return verifySignature(alg, keyObject(), data, signature);
82
+ if (alg !== "ecdsa-p256-sha256" || !subtle) return verifySignature(alg, keyObject(), data, signature);
92
83
  try {
93
84
  const jwk = isKeyObject(publicKey) ? publicKey.export({ format: "jwk" }) : publicKey;
94
- if (alg === "rsa-pss-sha512") {
95
- const key2 = await importRsaPssVerifyKey(subtle, jwk);
96
- return await subtle.verify(RSA_PSS_SHA512_SIGN, key2, toArrayBuffer(signature), toArrayBuffer(data));
97
- }
98
85
  const key = await importP256VerifyKey(subtle, jwk);
99
86
  return await subtle.verify(P256_SHA256, key, toArrayBuffer(signature), toArrayBuffer(data));
100
87
  } catch {
101
88
  return false;
102
89
  }
103
90
  }
104
- const RSA_PSS_SHA512 = { name: "RSA-PSS", hash: "SHA-512" };
105
- const RSA_PSS_SHA512_SIGN = { name: "RSA-PSS", saltLength: 64 };
106
- const rsaPssVerifyKeys = /* @__PURE__ */ new Map();
107
- function importRsaPssVerifyKey(subtle, jwk) {
108
- if (jwk.kty !== "RSA" || typeof jwk.n !== "string" || typeof jwk.e !== "string")
109
- return Promise.reject(new TypeError("Not an RSA public key"));
110
- const cacheKey = `${jwk.e}.${jwk.n}`;
111
- let key = rsaPssVerifyKeys.get(cacheKey);
112
- if (!key) {
113
- key = subtle.importKey("jwk", { kty: "RSA", n: jwk.n, e: jwk.e }, RSA_PSS_SHA512, false, ["verify"]);
114
- key.catch(() => rsaPssVerifyKeys.delete(cacheKey));
115
- if (rsaPssVerifyKeys.size >= 256) rsaPssVerifyKeys.delete(rsaPssVerifyKeys.keys().next().value);
116
- rsaPssVerifyKeys.set(cacheKey, key);
117
- }
118
- return key;
119
- }
120
91
  function isKeyObject(value) {
121
92
  return typeof value?.export === "function" && "asymmetricKeyType" in value;
122
93
  }
@@ -139,25 +110,11 @@ function signWithPrivateJwk(alg, privateJwk, data) {
139
110
  return new Uint8Array((0, import_crypto.sign)(null, data, (0, import_crypto.createPrivateKey)({ key: privateJwk, format: "jwk" })));
140
111
  if (alg === "ecdsa-p256-sha256")
141
112
  return new Uint8Array((0, import_crypto.sign)("sha256", data, { key: privateJwk, format: "jwk", dsaEncoding: "ieee-p1363" }));
142
- if (alg === "rsa-pss-sha512")
143
- return new Uint8Array(
144
- (0, import_crypto.sign)("sha512", data, {
145
- key: (0, import_crypto.createPrivateKey)({ key: privateJwk, format: "jwk" }),
146
- padding: import_crypto.constants.RSA_PKCS1_PSS_PADDING,
147
- saltLength: 64
148
- })
149
- );
150
113
  throw new TypeError(`Unsupported signing algorithm: ${alg}`);
151
114
  }
152
115
  async function signWithPrivateJwkAsync(alg, privateJwk, data) {
153
116
  const subtle = subtleCrypto();
154
- if (alg !== "ecdsa-p256-sha256" && alg !== "rsa-pss-sha512" || !subtle)
155
- return signWithPrivateJwk(alg, privateJwk, data);
156
- if (alg === "rsa-pss-sha512") {
157
- const { kty, n, e, d, p, q, dp, dq, qi } = privateJwk;
158
- const key2 = await subtle.importKey("jwk", { kty, n, e, d, p, q, dp, dq, qi }, RSA_PSS_SHA512, false, ["sign"]);
159
- return new Uint8Array(await subtle.sign(RSA_PSS_SHA512_SIGN, key2, toArrayBuffer(data)));
160
- }
117
+ if (alg !== "ecdsa-p256-sha256" || !subtle) return signWithPrivateJwk(alg, privateJwk, data);
161
118
  const key = await subtle.importKey("jwk", privateP256Jwk(privateJwk), P256, false, ["sign"]);
162
119
  return new Uint8Array(await subtle.sign(P256_SHA256, key, toArrayBuffer(data)));
163
120
  }
@@ -1,5 +1,4 @@
1
1
  import {
2
- constants,
3
2
  createPrivateKey,
4
3
  createPublicKey,
5
4
  sign as nodeSign,
@@ -38,14 +37,6 @@ function verifySignature(alg, publicKey, data, signature) {
38
37
  );
39
38
  }
40
39
  }
41
- if (alg === "rsa-pss-sha512") {
42
- return nodeVerify(
43
- "sha512",
44
- data,
45
- { key: publicKey, padding: constants.RSA_PKCS1_PSS_PADDING, saltLength: 64 },
46
- signature
47
- );
48
- }
49
40
  return false;
50
41
  } catch {
51
42
  return false;
@@ -66,36 +57,15 @@ function privateP256Jwk(jwk) {
66
57
  async function verifySignatureAsync(alg, publicKey, data, signature) {
67
58
  const subtle = subtleCrypto();
68
59
  const keyObject = () => isKeyObject(publicKey) ? publicKey : jwkToPublicKey(publicKey);
69
- if (alg !== "ecdsa-p256-sha256" && alg !== "rsa-pss-sha512" || !subtle)
70
- return verifySignature(alg, keyObject(), data, signature);
60
+ if (alg !== "ecdsa-p256-sha256" || !subtle) return verifySignature(alg, keyObject(), data, signature);
71
61
  try {
72
62
  const jwk = isKeyObject(publicKey) ? publicKey.export({ format: "jwk" }) : publicKey;
73
- if (alg === "rsa-pss-sha512") {
74
- const key2 = await importRsaPssVerifyKey(subtle, jwk);
75
- return await subtle.verify(RSA_PSS_SHA512_SIGN, key2, toArrayBuffer(signature), toArrayBuffer(data));
76
- }
77
63
  const key = await importP256VerifyKey(subtle, jwk);
78
64
  return await subtle.verify(P256_SHA256, key, toArrayBuffer(signature), toArrayBuffer(data));
79
65
  } catch {
80
66
  return false;
81
67
  }
82
68
  }
83
- const RSA_PSS_SHA512 = { name: "RSA-PSS", hash: "SHA-512" };
84
- const RSA_PSS_SHA512_SIGN = { name: "RSA-PSS", saltLength: 64 };
85
- const rsaPssVerifyKeys = /* @__PURE__ */ new Map();
86
- function importRsaPssVerifyKey(subtle, jwk) {
87
- if (jwk.kty !== "RSA" || typeof jwk.n !== "string" || typeof jwk.e !== "string")
88
- return Promise.reject(new TypeError("Not an RSA public key"));
89
- const cacheKey = `${jwk.e}.${jwk.n}`;
90
- let key = rsaPssVerifyKeys.get(cacheKey);
91
- if (!key) {
92
- key = subtle.importKey("jwk", { kty: "RSA", n: jwk.n, e: jwk.e }, RSA_PSS_SHA512, false, ["verify"]);
93
- key.catch(() => rsaPssVerifyKeys.delete(cacheKey));
94
- if (rsaPssVerifyKeys.size >= 256) rsaPssVerifyKeys.delete(rsaPssVerifyKeys.keys().next().value);
95
- rsaPssVerifyKeys.set(cacheKey, key);
96
- }
97
- return key;
98
- }
99
69
  function isKeyObject(value) {
100
70
  return typeof value?.export === "function" && "asymmetricKeyType" in value;
101
71
  }
@@ -118,25 +88,11 @@ function signWithPrivateJwk(alg, privateJwk, data) {
118
88
  return new Uint8Array(nodeSign(null, data, createPrivateKey({ key: privateJwk, format: "jwk" })));
119
89
  if (alg === "ecdsa-p256-sha256")
120
90
  return new Uint8Array(nodeSign("sha256", data, { key: privateJwk, format: "jwk", dsaEncoding: "ieee-p1363" }));
121
- if (alg === "rsa-pss-sha512")
122
- return new Uint8Array(
123
- nodeSign("sha512", data, {
124
- key: createPrivateKey({ key: privateJwk, format: "jwk" }),
125
- padding: constants.RSA_PKCS1_PSS_PADDING,
126
- saltLength: 64
127
- })
128
- );
129
91
  throw new TypeError(`Unsupported signing algorithm: ${alg}`);
130
92
  }
131
93
  async function signWithPrivateJwkAsync(alg, privateJwk, data) {
132
94
  const subtle = subtleCrypto();
133
- if (alg !== "ecdsa-p256-sha256" && alg !== "rsa-pss-sha512" || !subtle)
134
- return signWithPrivateJwk(alg, privateJwk, data);
135
- if (alg === "rsa-pss-sha512") {
136
- const { kty, n, e, d, p, q, dp, dq, qi } = privateJwk;
137
- const key2 = await subtle.importKey("jwk", { kty, n, e, d, p, q, dp, dq, qi }, RSA_PSS_SHA512, false, ["sign"]);
138
- return new Uint8Array(await subtle.sign(RSA_PSS_SHA512_SIGN, key2, toArrayBuffer(data)));
139
- }
95
+ if (alg !== "ecdsa-p256-sha256" || !subtle) return signWithPrivateJwk(alg, privateJwk, data);
140
96
  const key = await subtle.importKey("jwk", privateP256Jwk(privateJwk), P256, false, ["sign"]);
141
97
  return new Uint8Array(await subtle.sign(P256_SHA256, key, toArrayBuffer(data)));
142
98
  }
@@ -8,8 +8,8 @@ export declare const WEB_BOT_AUTH_PROFILE: "web-bot-auth";
8
8
  export declare const WEB_BOT_AUTH_DRAFT_REVISION: "draft-ietf-webbotauth-httpsig-protocol-00";
9
9
  export declare const SIGNATURE_DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
10
10
  export declare const SIGNATURE_DIRECTORY_MEDIA_TYPE = "application/http-message-signatures-directory+json";
11
- /** The profile's signature algorithms (wba-profile.mdx "Profile rules"). ES256 is RFC 9421 profile only. */
12
- export declare const WEB_BOT_AUTH_ALGORITHMS: readonly ["ed25519", "rsa-pss-sha512"];
11
+ /** The profile's signature algorithm: Ed25519 only. ES256 is RFC 9421 profile only. */
12
+ export declare const WEB_BOT_AUTH_ALGORITHMS: readonly ["ed25519"];
13
13
  /**
14
14
  * The profile's `Accept-Signature` member (RFC 9421 §5.1): the label, the
15
15
  * components the profile requires, and its parameters. A replay challenge
@@ -73,7 +73,12 @@ export declare function jwkThumbprint(jwk: Partial<Pick<AdcpJsonWebKey, 'kty' |
73
73
  * time rather than on the first request.
74
74
  */
75
75
  export declare function assertWebBotAuthSigningKey(key: WebBotSigningKey): void;
76
- /** Reject authority normalization differentials on received identifiers. */
76
+ /**
77
+ * A received identity origin, which must be exactly the canonical https
78
+ * origin string (WBA §5.2.1): lowercase host, no default port, and no path,
79
+ * trailing `/`, query, fragment, or userinfo. The string is compared as
80
+ * received, never normalized first.
81
+ */
77
82
  export declare function receivedAgentOrigin(value: string): string;
78
83
  export declare function getWebBotAuthLegacyOriginGate(resolver: Function | undefined): ((origin: string) => Promise<void>) | undefined;
79
84
  export interface WebBotAuthAuthorizationFailure {
@@ -136,8 +141,9 @@ export interface VerifyWebBotAuthOptions {
136
141
  /**
137
142
  * Verify a Web Bot Auth request (wba-profile.mdx "Verification"). Resolves to
138
143
  * exactly one verified signer: the profile takes one `web-bot-auth` signature
139
- * and one `Signature-Agent` member per request. The array shape is kept for
140
- * principal resolvers written against earlier drafts.
144
+ * and one `Signature-Agent` member per request, and does not read signatures
145
+ * with other tags. The array shape is kept for principal resolvers written
146
+ * against earlier drafts.
141
147
  */
142
148
  export declare function verifyWebBotAuth(request: RequestLike, options: VerifyWebBotAuthOptions): Promise<VerifiedWebBotSigner[]>;
143
149
  export interface VerifiedSignatureDirectory {
@@ -151,11 +157,13 @@ export interface VerifiedSignatureDirectory {
151
157
  }
152
158
  /**
153
159
  * Verify a key directory response (wba-profile.mdx "Key directories", WBA
154
- * Appendix B.1). The response must be signed once per key, each signature
155
- * covering `"@authority";req` and `content-digest` with `created`, `expires`,
156
- * the key's thumbprint as `keyid` and `tag="http-message-signatures-directory"`,
157
- * and verifying under the key the directory itself provides. An unsigned
158
- * directory is a discovery failure.
160
+ * Appendix B.1). The response must be signed once per usable key, each
161
+ * signature covering `"@authority";req` and `content-digest` with `created`,
162
+ * `expires`, the key's thumbprint as `keyid` and
163
+ * `tag="http-message-signatures-directory"`, and verifying under the key the
164
+ * directory itself provides. Keys the profile cannot use, and their
165
+ * signatures, are ignored. An unsigned directory, or one without a usable
166
+ * key, is a discovery failure.
159
167
  */
160
168
  export declare function verifyKeyDirectory(response: ResponseLike, now?: number, options?: {
161
169
  allowTestKeys?: boolean;
@@ -8,8 +8,8 @@ export declare const WEB_BOT_AUTH_PROFILE: "web-bot-auth";
8
8
  export declare const WEB_BOT_AUTH_DRAFT_REVISION: "draft-ietf-webbotauth-httpsig-protocol-00";
9
9
  export declare const SIGNATURE_DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
10
10
  export declare const SIGNATURE_DIRECTORY_MEDIA_TYPE = "application/http-message-signatures-directory+json";
11
- /** The profile's signature algorithms (wba-profile.mdx "Profile rules"). ES256 is RFC 9421 profile only. */
12
- export declare const WEB_BOT_AUTH_ALGORITHMS: readonly ["ed25519", "rsa-pss-sha512"];
11
+ /** The profile's signature algorithm: Ed25519 only. ES256 is RFC 9421 profile only. */
12
+ export declare const WEB_BOT_AUTH_ALGORITHMS: readonly ["ed25519"];
13
13
  /**
14
14
  * The profile's `Accept-Signature` member (RFC 9421 §5.1): the label, the
15
15
  * components the profile requires, and its parameters. A replay challenge
@@ -73,7 +73,12 @@ export declare function jwkThumbprint(jwk: Partial<Pick<AdcpJsonWebKey, 'kty' |
73
73
  * time rather than on the first request.
74
74
  */
75
75
  export declare function assertWebBotAuthSigningKey(key: WebBotSigningKey): void;
76
- /** Reject authority normalization differentials on received identifiers. */
76
+ /**
77
+ * A received identity origin, which must be exactly the canonical https
78
+ * origin string (WBA §5.2.1): lowercase host, no default port, and no path,
79
+ * trailing `/`, query, fragment, or userinfo. The string is compared as
80
+ * received, never normalized first.
81
+ */
77
82
  export declare function receivedAgentOrigin(value: string): string;
78
83
  export declare function getWebBotAuthLegacyOriginGate(resolver: Function | undefined): ((origin: string) => Promise<void>) | undefined;
79
84
  export interface WebBotAuthAuthorizationFailure {
@@ -136,8 +141,9 @@ export interface VerifyWebBotAuthOptions {
136
141
  /**
137
142
  * Verify a Web Bot Auth request (wba-profile.mdx "Verification"). Resolves to
138
143
  * exactly one verified signer: the profile takes one `web-bot-auth` signature
139
- * and one `Signature-Agent` member per request. The array shape is kept for
140
- * principal resolvers written against earlier drafts.
144
+ * and one `Signature-Agent` member per request, and does not read signatures
145
+ * with other tags. The array shape is kept for principal resolvers written
146
+ * against earlier drafts.
141
147
  */
142
148
  export declare function verifyWebBotAuth(request: RequestLike, options: VerifyWebBotAuthOptions): Promise<VerifiedWebBotSigner[]>;
143
149
  export interface VerifiedSignatureDirectory {
@@ -151,11 +157,13 @@ export interface VerifiedSignatureDirectory {
151
157
  }
152
158
  /**
153
159
  * Verify a key directory response (wba-profile.mdx "Key directories", WBA
154
- * Appendix B.1). The response must be signed once per key, each signature
155
- * covering `"@authority";req` and `content-digest` with `created`, `expires`,
156
- * the key's thumbprint as `keyid` and `tag="http-message-signatures-directory"`,
157
- * and verifying under the key the directory itself provides. An unsigned
158
- * directory is a discovery failure.
160
+ * Appendix B.1). The response must be signed once per usable key, each
161
+ * signature covering `"@authority";req` and `content-digest` with `created`,
162
+ * `expires`, the key's thumbprint as `keyid` and
163
+ * `tag="http-message-signatures-directory"`, and verifying under the key the
164
+ * directory itself provides. Keys the profile cannot use, and their
165
+ * signatures, are ignored. An unsigned directory, or one without a usable
166
+ * key, is a discovery failure.
159
167
  */
160
168
  export declare function verifyKeyDirectory(response: ResponseLike, now?: number, options?: {
161
169
  allowTestKeys?: boolean;
@@ -70,8 +70,9 @@ const WEB_BOT_AUTH_PROFILE = "web-bot-auth";
70
70
  const WEB_BOT_AUTH_DRAFT_REVISION = "draft-ietf-webbotauth-httpsig-protocol-00";
71
71
  const SIGNATURE_DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
72
72
  const SIGNATURE_DIRECTORY_MEDIA_TYPE = "application/http-message-signatures-directory+json";
73
- const WEB_BOT_AUTH_ALGORITHMS = Object.freeze(["ed25519", "rsa-pss-sha512"]);
73
+ const WEB_BOT_AUTH_ALGORITHMS = Object.freeze(["ed25519"]);
74
74
  const DIRECTORY_TAG = "http-message-signatures-directory";
75
+ const PRIVATE_JWK_MEMBERS = /* @__PURE__ */ new Set(["d", "p", "q", "dp", "dq", "qi", "oth", "k"]);
75
76
  const CLOCK_SKEW_SECONDS = 60;
76
77
  const MAX_LIFETIME_SECONDS = 300;
77
78
  const REQUIRED_PARAMETERS = ["created", "expires", "nonce", "keyid", "alg", "tag"];
@@ -182,26 +183,29 @@ function jwkThumbprint(jwk) {
182
183
  else return denied("unsupported_key");
183
184
  return (0, import_crypto.createHash)("sha256").update(JSON.stringify(fields)).digest("base64url");
184
185
  }
186
+ function keyAlgorithm(jwk) {
187
+ return jwk.kty === "OKP" && jwk.crv === "Ed25519" ? "ed25519" : void 0;
188
+ }
185
189
  function algorithm(jwk) {
186
- if (jwk.kty === "OKP" && jwk.crv === "Ed25519") return "ed25519";
187
- if (jwk.kty === "RSA" && typeof jwk.n === "string" && jwk.e === "AQAB") {
188
- const bytes = Buffer.from(jwk.n, "base64url").length;
189
- if (bytes >= 256 && bytes <= 512) return "rsa-pss-sha512";
190
- }
191
- return denied("unsupported_key");
190
+ return keyAlgorithm(jwk) ?? denied("unsupported_key");
192
191
  }
193
192
  function signingAlgorithm(jwk) {
194
193
  try {
195
194
  return algorithm(jwk);
196
195
  } 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
- );
196
+ throw new TypeError("Web Bot Auth signs with Ed25519 keys only; ES256 remains on the AdCP RFC 9421 profile");
200
197
  }
201
198
  }
202
199
  function assertWebBotAuthSigningKey(key) {
203
200
  signingAlgorithm("privateKey" in key ? key.privateKey : key.publicKey);
204
201
  }
202
+ function signingOrigin(value) {
203
+ try {
204
+ return receivedAgentOrigin((0, import_canonicalize.canonicalAgentOrigin)(value));
205
+ } catch {
206
+ throw new TypeError("Web Bot Auth signs as an https origin only (WBA section 5.2.1)");
207
+ }
208
+ }
205
209
  function inputs(headers, limit) {
206
210
  const members = dictionary((0, import_canonicalize2.getHeaderValue)(headers, "signature-input"), "Signature-Input");
207
211
  if (members.size > limit) bad("signature_input");
@@ -214,6 +218,7 @@ function inputs(headers, limit) {
214
218
  });
215
219
  }
216
220
  function receivedAgentOrigin(value) {
221
+ if (!value.startsWith("https://")) return denied("agent_origin");
217
222
  try {
218
223
  (0, import_select_agent.canonicalAgentUrl)(value);
219
224
  const origin = (0, import_canonicalize.canonicalAgentOrigin)(value);
@@ -436,8 +441,10 @@ async function verifyWebBotAuth(request, options) {
436
441
  if (!(member2[0] instanceof ArrayBuffer) || member2[1].size !== 0) bad("signature");
437
442
  for (const member2 of agents?.values() ?? []) if (typeof member2[0] !== "string") bad("signature_agent");
438
443
  if (!inputMembers || !signatureMembers) return denied("headers_missing");
439
- if (inputMembers.size !== 1 || signatureMembers.size !== 1) denied("signature_count");
440
- label = inputMembers.keys().next().value;
444
+ const tagged = [...inputMembers].filter(([, member2]) => member2[1].get("tag") === WEB_BOT_AUTH_PROFILE);
445
+ if (tagged.length === 0) denied("profile");
446
+ if (tagged.length > 1) denied("signature_count");
447
+ label = tagged[0][0];
441
448
  if (!signatureMembers.has(label)) denied("signature_label");
442
449
  try {
443
450
  (0, import_parser.parseSignature)(signatureHeader, label, "rfc8941-base64");
@@ -450,7 +457,6 @@ async function verifyWebBotAuth(request, options) {
450
457
  } catch {
451
458
  return denied("parameters");
452
459
  }
453
- if (input.params.tag !== WEB_BOT_AUTH_PROFILE) denied("profile");
454
460
  if (!agents || agents.size !== 1) denied(agents ? "agent_count" : "agent_missing");
455
461
  const agentMember = `signature-agent;key="${label}"`;
456
462
  const agentComponents = input.components.filter((c) => c.startsWith("signature-agent;"));
@@ -458,7 +464,7 @@ async function verifyWebBotAuth(request, options) {
458
464
  const member = agents.get(label);
459
465
  if (!member) denied("agent_not_covered");
460
466
  const type = member[1].get("type");
461
- if (type !== void 0 && type !== "directory") denied("agent_type");
467
+ if (type !== void 0 && String(type) !== "directory") denied("agent_type");
462
468
  const origin = receivedAgentOrigin(member[0]);
463
469
  const { created, expires, nonce, keyid, alg } = input.params;
464
470
  if ([created, expires, nonce, keyid, alg].some((value) => value === void 0)) denied("parameters_missing");
@@ -582,11 +588,13 @@ async function verifyKeyDirectory(response, now = Date.now() / 1e3, options = {}
582
588
  let expires = Infinity;
583
589
  for (const raw of document.keys) {
584
590
  if (!raw || typeof raw !== "object") denied("directory_key");
591
+ if (Object.keys(raw).some((k) => PRIVATE_JWK_MEMBERS.has(k.toLowerCase()) || k.startsWith("_private")))
592
+ denied("directory_key");
593
+ if (!keyAlgorithm(raw)) continue;
585
594
  const thumbprint = jwkThumbprint(raw);
586
595
  const key = { ...raw, kid: raw.kid ?? thumbprint };
587
- if (key.kid !== thumbprint || kids.has(key.kid) || key.d !== void 0 || Object.keys(key).some((k) => k.startsWith("_private")) || key.adcp_use !== void 0 || !testKeysAllowed(options.allowTestKeys) && TEST_KIDS.has(key.kid))
596
+ if (key.kid !== thumbprint || kids.has(key.kid) || key.adcp_use !== void 0 || !testKeysAllowed(options.allowTestKeys) && TEST_KIDS.has(key.kid))
588
597
  denied("directory_key");
589
- algorithm(key);
590
598
  kids.add(key.kid);
591
599
  keys.push(key);
592
600
  const proof = signatures.find((input) => input.params.keyid === key.kid && input.params.tag === DIRECTORY_TAG);
@@ -629,6 +637,7 @@ async function verifyKeyDirectory(response, now = Date.now() / 1e3, options = {}
629
637
  );
630
638
  expires = Math.min(expires, proof.params.expires);
631
639
  }
640
+ if (!keys.length) denied("directory_keys");
632
641
  return { keys, expires, signed: true, proof: response };
633
642
  } catch (error) {
634
643
  if (error instanceof WebBotAuthError) {
@@ -758,7 +767,7 @@ async function signWebBotAuth(request, key, options) {
758
767
  throw new TypeError(
759
768
  "Web Bot Auth relays were removed from the profile (adcp#7894): an intermediary signs as itself with one signature"
760
769
  );
761
- const origin = (0, import_canonicalize.canonicalAgentOrigin)(options.origin);
770
+ const origin = signingOrigin(options.origin);
762
771
  const label = options.label ?? "sig1";
763
772
  if (!/^[a-z][a-z0-9_.*-]*$/.test(label)) throw new TypeError("Invalid signature label");
764
773
  const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
@@ -813,7 +822,7 @@ async function signWebBotAuth(request, key, options) {
813
822
  return headers;
814
823
  }
815
824
  async function createSignedSignatureDirectory(originUrl, signingKeys, options = {}) {
816
- const origin = (0, import_canonicalize.canonicalAgentOrigin)(originUrl);
825
+ const origin = signingOrigin(originUrl);
817
826
  const now = Math.floor((options.now ?? (() => Date.now() / 1e3))());
818
827
  const lifetime = options.lifetimeSeconds ?? 86400;
819
828
  if (!Number.isFinite(now) || !Number.isSafeInteger(lifetime) || lifetime < 1 || !Number.isSafeInteger(now + lifetime))
@@ -822,12 +831,7 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
822
831
  const keys = signingKeys.map((key) => {
823
832
  const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
824
833
  signingAlgorithm(jwk);
825
- return {
826
- kty: jwk.kty,
827
- ...jwk.kty === "RSA" ? { n: jwk.n, e: jwk.e } : { crv: jwk.crv, x: jwk.x },
828
- kid: jwkThumbprint(jwk),
829
- use: "sig"
830
- };
834
+ return { kty: jwk.kty, crv: jwk.crv, x: jwk.x, kid: jwkThumbprint(jwk), use: "sig" };
831
835
  });
832
836
  if (new Set(keys.map((k) => k.kid)).size !== keys.length) throw new TypeError("Duplicate directory key");
833
837
  const body = JSON.stringify({ keys });
@@ -873,6 +877,7 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
873
877
  }
874
878
  function createWebBotAuthFetch(upstream, key, options) {
875
879
  assertWebBotAuthSigningKey(key);
880
+ signingOrigin(options.origin);
876
881
  return async (input, init) => {
877
882
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
878
883
  if (options.shouldSign && !options.shouldSign(url, init)) return upstream(input, init);