@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.
- package/README.md +2 -2
- package/dist/lib/identity-3.3/revocation.js +6 -7
- package/dist/lib/identity-3.3/revocation.mjs +6 -7
- package/dist/lib/server/create-adcp-server.js +1 -1
- package/dist/lib/server/create-adcp-server.mjs +1 -1
- package/dist/lib/signing/crypto.d.mts +1 -1
- package/dist/lib/signing/crypto.d.ts +1 -1
- package/dist/lib/signing/crypto.js +2 -45
- package/dist/lib/signing/crypto.mjs +2 -46
- package/dist/lib/signing/web-bot-auth.d.mts +18 -10
- package/dist/lib/signing/web-bot-auth.d.ts +18 -10
- package/dist/lib/signing/web-bot-auth.js +29 -24
- package/dist/lib/signing/web-bot-auth.mjs +29 -24
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/v2/projection/catalog.d.mts +4 -0
- package/dist/lib/v2/projection/catalog.d.ts +4 -0
- package/dist/lib/v2/projection/creative-delivery.d.mts +15 -9
- package/dist/lib/v2/projection/creative-delivery.d.ts +15 -9
- package/dist/lib/v2/projection/creative-delivery.js +71 -10
- package/dist/lib/v2/projection/creative-delivery.mjs +71 -10
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/TYPE-SUMMARY.md +1 -1
- package/docs/guides/CREATIVE-DELIVERY.md +18 -1
- package/docs/guides/REPORTING-STABILITY.md +7 -7
- package/docs/llms.txt +2 -2
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/docs/migration-agent-identity-3.3.md +44 -19
- package/docs/migration-reporting-storage-package.md +3 -3
- package/docs/releases/16.0.0.md +20 -0
- 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 #
|
|
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
|
|
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"
|
|
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,
|
|
238
|
-
algorithms: [
|
|
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 =
|
|
300
|
-
if (
|
|
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"
|
|
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,
|
|
217
|
-
algorithms: [
|
|
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 =
|
|
279
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
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"
|
|
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"
|
|
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"
|
|
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
|
|
12
|
-
export declare const WEB_BOT_AUTH_ALGORITHMS: readonly ["ed25519"
|
|
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
|
-
/**
|
|
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
|
|
140
|
-
* principal resolvers written
|
|
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
|
|
155
|
-
* covering `"@authority";req` and `content-digest` with `created`,
|
|
156
|
-
* the key's thumbprint as `keyid` and
|
|
157
|
-
* and verifying under the key the
|
|
158
|
-
* directory
|
|
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
|
|
12
|
-
export declare const WEB_BOT_AUTH_ALGORITHMS: readonly ["ed25519"
|
|
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
|
-
/**
|
|
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
|
|
140
|
-
* principal resolvers written
|
|
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
|
|
155
|
-
* covering `"@authority";req` and `content-digest` with `created`,
|
|
156
|
-
* the key's thumbprint as `keyid` and
|
|
157
|
-
* and verifying under the key the
|
|
158
|
-
* directory
|
|
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"
|
|
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
|
-
|
|
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
|
-
|
|
440
|
-
|
|
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.
|
|
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 = (
|
|
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 = (
|
|
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);
|