@adcp/sdk 16.0.0-beta.1 → 16.0.0-beta.3
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 +54 -0
- package/dist/lib/core/ConversationTypes.d.mts +8 -3
- package/dist/lib/core/ConversationTypes.d.ts +8 -3
- package/dist/lib/core/SingleAgentClient.d.mts +15 -4
- package/dist/lib/core/SingleAgentClient.d.ts +15 -4
- package/dist/lib/core/SingleAgentClient.js +29 -8
- package/dist/lib/core/SingleAgentClient.mjs +29 -8
- package/dist/lib/core/TaskExecutor.js +5 -1
- package/dist/lib/core/TaskExecutor.mjs +5 -1
- package/dist/lib/errors/index.d.mts +9 -4
- package/dist/lib/errors/index.d.ts +9 -4
- package/dist/lib/identity-3.3/generated-compat.d.mts +34 -0
- package/dist/lib/identity-3.3/generated-compat.d.ts +34 -0
- package/dist/lib/identity-3.3/generated-compat.js +16 -0
- package/dist/lib/identity-3.3/generated-compat.mjs +0 -0
- package/dist/lib/identity-3.3/index.d.mts +2 -2
- package/dist/lib/identity-3.3/index.d.ts +2 -2
- package/dist/lib/identity-3.3/index.js +4 -0
- package/dist/lib/identity-3.3/index.mjs +5 -1
- package/dist/lib/identity-3.3/lifecycle.d.mts +17 -5
- package/dist/lib/identity-3.3/lifecycle.d.ts +17 -5
- package/dist/lib/identity-3.3/lifecycle.js +32 -5
- package/dist/lib/identity-3.3/lifecycle.mjs +38 -6
- package/dist/lib/identity-3.3/revocation.d.mts +39 -3
- package/dist/lib/identity-3.3/revocation.d.ts +39 -3
- package/dist/lib/identity-3.3/revocation.js +88 -18
- package/dist/lib/identity-3.3/revocation.mjs +89 -19
- package/dist/lib/identity-3.3/server.d.mts +12 -0
- package/dist/lib/identity-3.3/server.d.ts +12 -0
- package/dist/lib/identity-3.3/server.js +103 -43
- package/dist/lib/identity-3.3/server.mjs +111 -45
- package/dist/lib/identity-3.3/trust.d.mts +62 -1
- package/dist/lib/identity-3.3/trust.d.ts +62 -1
- package/dist/lib/identity-3.3/trust.js +126 -21
- package/dist/lib/identity-3.3/trust.mjs +124 -21
- package/dist/lib/identity-3.3/types.d.mts +39 -11
- package/dist/lib/identity-3.3/types.d.ts +39 -11
- package/dist/lib/index.d.mts +3 -3
- package/dist/lib/index.d.ts +3 -3
- package/dist/lib/index.js +8 -0
- package/dist/lib/index.mjs +9 -1
- package/dist/lib/negotiation/types.d.mts +20 -39
- package/dist/lib/negotiation/types.d.ts +20 -39
- package/dist/lib/protocols/index.d.mts +61 -25
- package/dist/lib/protocols/index.d.ts +61 -25
- package/dist/lib/protocols/index.js +11 -6
- package/dist/lib/protocols/index.mjs +5 -2
- package/dist/lib/protocols/mcp-modern.js +1 -0
- package/dist/lib/protocols/mcp-modern.mjs +2 -1
- package/dist/lib/protocols/mcp.js +1 -0
- package/dist/lib/protocols/mcp.mjs +2 -1
- package/dist/lib/protocols/responseSizeLimit.d.mts +91 -5
- package/dist/lib/protocols/responseSizeLimit.d.ts +91 -5
- package/dist/lib/protocols/responseSizeLimit.js +195 -31
- package/dist/lib/protocols/responseSizeLimit.mjs +192 -31
- package/dist/lib/reporting/bigquery/index.js +8 -0
- package/dist/lib/reporting/bigquery/index.mjs +8 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/auth-signature.d.mts +6 -1
- package/dist/lib/server/auth-signature.d.ts +6 -1
- package/dist/lib/server/auth-signature.js +24 -13
- package/dist/lib/server/auth-signature.mjs +29 -13
- package/dist/lib/server/auth.d.mts +2 -2
- package/dist/lib/server/auth.d.ts +2 -2
- package/dist/lib/server/create-adcp-server.js +31 -10
- package/dist/lib/server/create-adcp-server.mjs +37 -11
- package/dist/lib/signing/crypto.d.mts +15 -0
- package/dist/lib/signing/crypto.d.ts +15 -0
- package/dist/lib/signing/crypto.js +83 -3
- package/dist/lib/signing/crypto.mjs +86 -3
- package/dist/lib/signing/fetch-async.js +1 -1
- package/dist/lib/signing/fetch-async.mjs +1 -1
- package/dist/lib/signing/fetch.js +1 -1
- package/dist/lib/signing/fetch.mjs +1 -1
- package/dist/lib/signing/middleware.js +9 -0
- package/dist/lib/signing/middleware.mjs +14 -0
- package/dist/lib/signing/parser.d.mts +8 -5
- package/dist/lib/signing/parser.d.ts +8 -5
- package/dist/lib/signing/parser.js +77 -61
- package/dist/lib/signing/parser.mjs +77 -61
- package/dist/lib/signing/revocation.js +3 -0
- package/dist/lib/signing/revocation.mjs +3 -0
- package/dist/lib/signing/server.d.mts +1 -1
- package/dist/lib/signing/server.d.ts +1 -1
- package/dist/lib/signing/server.js +2 -0
- package/dist/lib/signing/server.mjs +2 -1
- package/dist/lib/signing/signer.js +2 -8
- package/dist/lib/signing/signer.mjs +3 -9
- package/dist/lib/signing/testing.js +2 -6
- package/dist/lib/signing/testing.mjs +3 -7
- package/dist/lib/signing/verifier.js +3 -3
- package/dist/lib/signing/verifier.mjs +4 -4
- package/dist/lib/signing/web-bot-auth.d.mts +84 -10
- package/dist/lib/signing/web-bot-auth.d.ts +84 -10
- package/dist/lib/signing/web-bot-auth.js +278 -88
- package/dist/lib/signing/web-bot-auth.mjs +279 -94
- package/dist/lib/signing/webhook-verifier.js +3 -3
- package/dist/lib/signing/webhook-verifier.mjs +4 -4
- package/dist/lib/substitution/compile.js +2 -1
- package/dist/lib/substitution/compile.mjs +2 -1
- package/dist/lib/substitution/consent-macros.d.mts +4 -0
- package/dist/lib/substitution/consent-macros.d.ts +4 -0
- package/dist/lib/substitution/consent-macros.js +45 -0
- package/dist/lib/substitution/consent-macros.mjs +20 -0
- package/dist/lib/substitution/index.d.mts +1 -1
- package/dist/lib/substitution/index.d.ts +1 -1
- package/dist/lib/substitution/index.js +2 -0
- package/dist/lib/substitution/index.mjs +2 -1
- package/dist/lib/substitution/translate.d.mts +15 -2
- package/dist/lib/substitution/translate.d.ts +15 -2
- package/dist/lib/substitution/translate.js +148 -26
- package/dist/lib/substitution/translate.mjs +147 -26
- package/dist/lib/testing/client.js +2 -1
- package/dist/lib/testing/client.mjs +2 -1
- package/dist/lib/testing/scenarios/capabilities.js +1 -1
- package/dist/lib/testing/scenarios/capabilities.mjs +1 -1
- package/dist/lib/testing/storyboard/request-signing/builder.js +2 -5
- package/dist/lib/testing/storyboard/request-signing/builder.mjs +3 -6
- package/dist/lib/testing/storyboard/signer-grader/grader.js +2 -7
- package/dist/lib/testing/storyboard/signer-grader/grader.mjs +2 -7
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/types/enums.generated.d.mts +3 -0
- package/dist/lib/types/enums.generated.d.ts +3 -0
- package/dist/lib/types/enums.generated.js +9 -0
- package/dist/lib/types/enums.generated.mjs +6 -0
- package/dist/lib/v2/projection/catalog-snapshot.js +3 -1
- package/dist/lib/v2/projection/catalog-snapshot.mjs +3 -1
- package/dist/lib/v2/projection/creative-delivery.d.mts +31 -9
- package/dist/lib/v2/projection/creative-delivery.d.ts +31 -9
- package/dist/lib/v2/projection/index.d.mts +1 -1
- package/dist/lib/v2/projection/index.d.ts +1 -1
- package/dist/lib/v2/projection/index.js +2 -0
- package/dist/lib/v2/projection/index.mjs +2 -0
- package/dist/lib/v2/projection/legacy-routes.d.mts +10 -0
- package/dist/lib/v2/projection/legacy-routes.d.ts +10 -0
- package/dist/lib/v2/projection/legacy-routes.js +7 -0
- package/dist/lib/v2/projection/legacy-routes.mjs +6 -0
- package/dist/lib/validation/schema-loader.js +25 -0
- package/dist/lib/validation/schema-loader.mjs +24 -0
- 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 +7 -2
- package/docs/guides/DISCOVERY-CACHE-AND-SESSION-REUSE.md +3 -2
- package/docs/guides/REPORTING-LEDGER.md +15 -5
- package/docs/guides/REPORTING-OPERATIONS.md +17 -11
- package/docs/guides/UNIVERSAL-MACRO-COMPILATION.md +38 -0
- package/docs/guides/VALIDATE-YOUR-AGENT.md +12 -0
- package/docs/llms.txt +2 -2
- package/docs/migration-12-to-13.md +8 -0
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/docs/migration-agent-identity-3.3.md +150 -11
- package/package.json +4 -1
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { toReleasePrecisionVersion } from "../version.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
constants as cryptoConstants,
|
|
4
|
+
createHash,
|
|
5
|
+
createPrivateKey,
|
|
6
|
+
randomBytes,
|
|
7
|
+
sign as nodeSign
|
|
8
|
+
} from "crypto";
|
|
3
9
|
import { parseDictionary, parseItem, serializeItem } from "structured-headers";
|
|
4
10
|
import { ssrfSafeFetch } from "../net/index.mjs";
|
|
5
11
|
import { eTldPlusOne } from "./agent-resolver/etld.mjs";
|
|
6
12
|
import { canonicalAgentOrigin } from "./agent-resolver/canonicalize.mjs";
|
|
7
13
|
import { canonicalAgentUrl } from "./agent-resolver/select-agent.mjs";
|
|
8
14
|
import { parseStrictJson } from "./agent-resolver/strict-json.mjs";
|
|
9
|
-
import {
|
|
10
|
-
canonicalAuthority,
|
|
11
|
-
canonicalTargetUri,
|
|
12
|
-
getHeaderValue
|
|
13
|
-
} from "./canonicalize.mjs";
|
|
15
|
+
import { getHeaderValue } from "./canonicalize.mjs";
|
|
14
16
|
import { computeContentDigest, contentDigestMatches, contentDigestUsesEncoding } from "./content-digest.mjs";
|
|
15
|
-
import { jwkToPublicKey, verifySignature } from "./crypto.mjs";
|
|
17
|
+
import { jwkToPublicKey, signWithPrivateJwkAsync, verifySignature, verifySignatureAsync } from "./crypto.mjs";
|
|
16
18
|
import {
|
|
17
19
|
parseSignature,
|
|
18
20
|
parseSignatureInput,
|
|
@@ -23,12 +25,14 @@ const WEB_BOT_AUTH_DRAFT_REVISION = "draft-ietf-webbotauth-httpsig-protocol-00";
|
|
|
23
25
|
const SIGNATURE_DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
|
|
24
26
|
const SIGNATURE_DIRECTORY_MEDIA_TYPE = "application/http-message-signatures-directory+json";
|
|
25
27
|
const DIRECTORY_TAG = "http-message-signatures-directory";
|
|
28
|
+
const DIRECTORY_CLOCK_SKEW_SECONDS = 60;
|
|
26
29
|
const TEST_KIDS = /* @__PURE__ */ new Set([
|
|
27
30
|
"6WsKMCObba49V3uN0vIENnk1BKAk7c9zjBJcWKyQ9_I",
|
|
28
31
|
"hHurKlVBnPJ4AGiPyl6HWMhxgkIcRBzaC8qWwEtnuVI",
|
|
29
32
|
"ZtaFf5jj-EzorJmZn6mX3YnmK_QyZKVLbbinzIQKOjQ",
|
|
30
33
|
"1XlJ3EIte7bPOEqKQtKrKNTNy5hywTyrwFXWrMbXKqE"
|
|
31
34
|
]);
|
|
35
|
+
const NO_CHALLENGE_REASONS = /* @__PURE__ */ new Set(["key_revoked", "identity_not_bound", "profile_disabled", "authorization_failed"]);
|
|
32
36
|
class WebBotAuthError extends Error {
|
|
33
37
|
constructor(status, reason) {
|
|
34
38
|
super(
|
|
@@ -37,13 +41,7 @@ class WebBotAuthError extends Error {
|
|
|
37
41
|
this.status = status;
|
|
38
42
|
this.reason = reason;
|
|
39
43
|
this.name = "WebBotAuthError";
|
|
40
|
-
this.headers = status === 403 && !
|
|
41
|
-
"key_revoked",
|
|
42
|
-
"identity_not_bound",
|
|
43
|
-
"revocation_unavailable",
|
|
44
|
-
"revocation_persistence_failed",
|
|
45
|
-
"profile_disabled"
|
|
46
|
-
].includes(reason) ? {
|
|
44
|
+
this.headers = status === 403 && !NO_CHALLENGE_REASONS.has(reason) ? {
|
|
47
45
|
"Accept-Signature": 'sig1=("@method" "@target-uri" "@authority" "content-type" "content-digest" "signature-agent";key="sig1");created;expires;nonce;keyid;tag="web-bot-auth"'
|
|
48
46
|
} : status === 429 ? { "Retry-After": "60" } : {};
|
|
49
47
|
}
|
|
@@ -51,18 +49,29 @@ class WebBotAuthError extends Error {
|
|
|
51
49
|
reason;
|
|
52
50
|
headers;
|
|
53
51
|
get body() {
|
|
54
|
-
if (
|
|
55
|
-
this.reason
|
|
56
|
-
))
|
|
52
|
+
if (this.reason === "authorization_failed" || this.reason === "identity_not_bound")
|
|
57
53
|
return {
|
|
58
|
-
adcp_error: {
|
|
59
|
-
code: this.reason.startsWith("revocation_") ? "SERVICE_UNAVAILABLE" : "PERMISSION_DENIED",
|
|
60
|
-
message: this.message,
|
|
61
|
-
recovery: this.reason.startsWith("revocation_") ? "transient" : "correctable"
|
|
62
|
-
}
|
|
54
|
+
adcp_error: { code: "PERMISSION_DENIED", message: "Agent authorization failed", recovery: "correctable" }
|
|
63
55
|
};
|
|
64
|
-
return {
|
|
56
|
+
return { adcp_error: { code: "AUTH_INVALID", message: this.message, recovery: "terminal" } };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The signer's identity could not be established because key discovery
|
|
60
|
+
* failed (WBA §6.10) or its revocation status is unknown. The request is
|
|
61
|
+
* unverified; servers apply their unverified-request policy.
|
|
62
|
+
*/
|
|
63
|
+
get unverified() {
|
|
64
|
+
return this.reason.startsWith("directory_") || this.reason === "identity_unverified";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
class WebBotAuthUnavailableError extends Error {
|
|
68
|
+
constructor(reason, cause) {
|
|
69
|
+
super("Web Bot Auth verification dependency is unavailable");
|
|
70
|
+
this.reason = reason;
|
|
71
|
+
this.name = "WebBotAuthUnavailableError";
|
|
72
|
+
if (cause !== void 0) Object.defineProperty(this, "cause", { value: cause });
|
|
65
73
|
}
|
|
74
|
+
reason;
|
|
66
75
|
}
|
|
67
76
|
function bad(reason) {
|
|
68
77
|
throw new WebBotAuthError(400, reason);
|
|
@@ -71,7 +80,8 @@ function denied(reason) {
|
|
|
71
80
|
throw new WebBotAuthError(403, reason);
|
|
72
81
|
}
|
|
73
82
|
function dictionary(value, field) {
|
|
74
|
-
if (
|
|
83
|
+
if (value === void 0 || value === "") return denied("headers_missing");
|
|
84
|
+
if (value.length > 64 * 1024) return bad("headers");
|
|
75
85
|
try {
|
|
76
86
|
rejectDuplicateDictionaryKeys(value, field);
|
|
77
87
|
return parseDictionary(value);
|
|
@@ -91,12 +101,18 @@ function jwkThumbprint(jwk) {
|
|
|
91
101
|
fields = { crv: jwk.crv, kty: jwk.kty, x: jwk.x };
|
|
92
102
|
else if (jwk.kty === "EC" && typeof jwk.crv === "string" && typeof jwk.x === "string" && typeof jwk.y === "string")
|
|
93
103
|
fields = { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y };
|
|
104
|
+
else if (jwk.kty === "RSA" && typeof jwk.e === "string" && typeof jwk.n === "string")
|
|
105
|
+
fields = { e: jwk.e, kty: jwk.kty, n: jwk.n };
|
|
94
106
|
else return denied("unsupported_key");
|
|
95
107
|
return createHash("sha256").update(JSON.stringify(fields)).digest("base64url");
|
|
96
108
|
}
|
|
97
109
|
function algorithm(jwk) {
|
|
98
110
|
if (jwk.kty === "OKP" && jwk.crv === "Ed25519") return "ed25519";
|
|
99
111
|
if (jwk.kty === "EC" && jwk.crv === "P-256") return "ecdsa-p256-sha256";
|
|
112
|
+
if (jwk.kty === "RSA" && typeof jwk.n === "string" && jwk.e === "AQAB") {
|
|
113
|
+
const bytes = Buffer.from(jwk.n, "base64url").length;
|
|
114
|
+
if (bytes >= 256 && bytes <= 512) return "rsa-pss-sha512";
|
|
115
|
+
}
|
|
100
116
|
return denied("unsupported_key");
|
|
101
117
|
}
|
|
102
118
|
function inputs(headers) {
|
|
@@ -140,8 +156,8 @@ function buildWebBotAuthSignatureBase(input, request, response) {
|
|
|
140
156
|
if (params.has("key")) return bad("derived_dictionary");
|
|
141
157
|
if (response && bare !== "@status" && !params.has("req")) return bad("response_component");
|
|
142
158
|
if (bare === "@method") value = request.method.toUpperCase();
|
|
143
|
-
else if (bare === "@target-uri") value =
|
|
144
|
-
else if (bare === "@authority") value =
|
|
159
|
+
else if (bare === "@target-uri") value = receivedTargetUri(request.url);
|
|
160
|
+
else if (bare === "@authority") value = receivedAuthority(request.url);
|
|
145
161
|
else if (bare === "@status" && response && !params.has("req")) value = String(response.status);
|
|
146
162
|
else return bad("derived_component");
|
|
147
163
|
} else {
|
|
@@ -161,21 +177,49 @@ function buildWebBotAuthSignatureBase(input, request, response) {
|
|
|
161
177
|
});
|
|
162
178
|
return [...lines, `"@signature-params": ${input.signatureParamsValue}`].join("\n");
|
|
163
179
|
}
|
|
180
|
+
function receivedTargetUri(url) {
|
|
181
|
+
try {
|
|
182
|
+
new URL(url);
|
|
183
|
+
} catch {
|
|
184
|
+
return bad("target_uri");
|
|
185
|
+
}
|
|
186
|
+
return url;
|
|
187
|
+
}
|
|
188
|
+
function receivedAuthority(url) {
|
|
189
|
+
try {
|
|
190
|
+
return new URL(url).host.toLowerCase();
|
|
191
|
+
} catch {
|
|
192
|
+
return bad("target_uri");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
164
195
|
function assertDigest(headers, body) {
|
|
165
196
|
const digest = getHeaderValue(headers, "content-digest");
|
|
166
197
|
if (!digest || !contentDigestUsesEncoding(digest, "rfc8941-base64") || !contentDigestMatches(digest, body))
|
|
167
198
|
denied("content_digest");
|
|
168
199
|
}
|
|
169
|
-
function
|
|
200
|
+
function signatureBytes(input, request, jwk, response) {
|
|
170
201
|
const alg = algorithm(jwk);
|
|
171
202
|
if (input.params.alg !== void 0 && input.params.alg !== alg) denied("algorithm");
|
|
172
203
|
const signature = getHeaderValue(response?.headers ?? request.headers, "signature");
|
|
173
|
-
|
|
204
|
+
if (signature === void 0 || signature === "") denied("headers_missing");
|
|
174
205
|
try {
|
|
175
|
-
bytes
|
|
206
|
+
return { alg, bytes: parseSignature(signature ?? "", input.label, "rfc8941-base64").bytes };
|
|
176
207
|
} catch {
|
|
177
208
|
return bad("signature");
|
|
178
209
|
}
|
|
210
|
+
}
|
|
211
|
+
async function verifyBytesAsync(input, request, jwk) {
|
|
212
|
+
const { alg, bytes } = signatureBytes(input, request, jwk);
|
|
213
|
+
let valid = false;
|
|
214
|
+
try {
|
|
215
|
+
valid = await verifySignatureAsync(alg, jwk, Buffer.from(buildWebBotAuthSignatureBase(input, request)), bytes);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (error instanceof WebBotAuthError) throw error;
|
|
218
|
+
}
|
|
219
|
+
if (!valid) denied("signature_invalid");
|
|
220
|
+
}
|
|
221
|
+
function verifyBytes(input, request, jwk, response) {
|
|
222
|
+
const { alg, bytes } = signatureBytes(input, request, jwk, response);
|
|
179
223
|
let valid = false;
|
|
180
224
|
try {
|
|
181
225
|
valid = verifySignature(
|
|
@@ -196,16 +240,63 @@ function registerWebBotAuthLegacyOriginGate(resolver, gate) {
|
|
|
196
240
|
function getWebBotAuthLegacyOriginGate(resolver) {
|
|
197
241
|
return resolver && legacyOriginGates.get(resolver);
|
|
198
242
|
}
|
|
243
|
+
const revocationCheckingResolvers = /* @__PURE__ */ new WeakSet();
|
|
244
|
+
function registerWebBotAuthRevocationCheck(resolver) {
|
|
245
|
+
revocationCheckingResolvers.add(resolver);
|
|
246
|
+
}
|
|
247
|
+
function isWebBotAuthRevocationChecked(resolver) {
|
|
248
|
+
return !!resolver && revocationCheckingResolvers.has(resolver);
|
|
249
|
+
}
|
|
250
|
+
function webBotAuthUnavailableResponse() {
|
|
251
|
+
return {
|
|
252
|
+
status: 503,
|
|
253
|
+
headers: { "Retry-After": "60" },
|
|
254
|
+
body: {
|
|
255
|
+
adcp_error: {
|
|
256
|
+
code: "SERVICE_UNAVAILABLE",
|
|
257
|
+
message: "Agent identity verification is temporarily unavailable",
|
|
258
|
+
recovery: "transient"
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
199
263
|
const authorizationFailures = /* @__PURE__ */ new WeakSet();
|
|
264
|
+
const AUTHORIZATION_FAILURE_MESSAGES = {
|
|
265
|
+
key_revoked: "Agent identity key is revoked",
|
|
266
|
+
identity_not_bound: "Agent identity is not onboarded for this operation",
|
|
267
|
+
revocation_unavailable: "Identity revocation information is unavailable",
|
|
268
|
+
authorization_failed: "Agent authorization failed",
|
|
269
|
+
authorization_unavailable: "Agent authorization is temporarily unavailable"
|
|
270
|
+
};
|
|
200
271
|
function createWebBotAuthAuthorizationFailure(reason) {
|
|
272
|
+
const transient = reason === "revocation_unavailable" || reason === "authorization_unavailable";
|
|
201
273
|
const failure = Object.freeze({
|
|
202
|
-
code:
|
|
203
|
-
message: reason
|
|
204
|
-
recovery:
|
|
274
|
+
code: transient ? "SERVICE_UNAVAILABLE" : "PERMISSION_DENIED",
|
|
275
|
+
message: AUTHORIZATION_FAILURE_MESSAGES[reason],
|
|
276
|
+
recovery: transient ? "transient" : "correctable"
|
|
205
277
|
});
|
|
206
278
|
authorizationFailures.add(failure);
|
|
207
279
|
return failure;
|
|
208
280
|
}
|
|
281
|
+
function principalFailureIsTransient(error) {
|
|
282
|
+
const value = error;
|
|
283
|
+
if (value?.transient === true || value?.recovery === "transient" || value?.code === "SERVICE_UNAVAILABLE")
|
|
284
|
+
return true;
|
|
285
|
+
return typeof value?.code !== "string";
|
|
286
|
+
}
|
|
287
|
+
function webBotAuthPrincipalFailureError(error) {
|
|
288
|
+
return principalFailureIsTransient(error) ? new WebBotAuthUnavailableError("authorization_unavailable", error) : new WebBotAuthError(403, "authorization_failed");
|
|
289
|
+
}
|
|
290
|
+
function deferWebBotAuthPrincipalFailure(signers, error) {
|
|
291
|
+
const signer = signers[signers.length - 1];
|
|
292
|
+
const authorizationFailure = createWebBotAuthAuthorizationFailure(
|
|
293
|
+
principalFailureIsTransient(error) ? "authorization_unavailable" : "authorization_failed"
|
|
294
|
+
);
|
|
295
|
+
return {
|
|
296
|
+
principal: `anonymous:${createHash("sha256").update(signer.origin).digest("hex")}`,
|
|
297
|
+
authorizationFailure
|
|
298
|
+
};
|
|
299
|
+
}
|
|
209
300
|
function getWebBotAuthAuthorizationFailure(extra) {
|
|
210
301
|
for (const value of [extra?.webBotAuthAuthorization, extra?.identity33]) {
|
|
211
302
|
const claim = value;
|
|
@@ -271,7 +362,8 @@ async function verifyWebBotAuth(request, options) {
|
|
|
271
362
|
const memberKey = component[1].get("key");
|
|
272
363
|
if (typeof memberKey !== "string" || memberKey !== input.label) bad("agent_member");
|
|
273
364
|
const member = agents.get(memberKey);
|
|
274
|
-
if (!member
|
|
365
|
+
if (!member) denied("agent_not_covered");
|
|
366
|
+
if (typeof member[0] !== "string") bad("signature_agent");
|
|
275
367
|
const type = member[1].get("type");
|
|
276
368
|
if (type !== void 0 && type !== "directory") denied("agent_type");
|
|
277
369
|
const origin = receivedAgentOrigin(member[0]);
|
|
@@ -291,27 +383,16 @@ async function verifyWebBotAuth(request, options) {
|
|
|
291
383
|
assertDigest(request.headers, request.body);
|
|
292
384
|
}
|
|
293
385
|
await options.directory.resolve(keyid, origin);
|
|
294
|
-
const
|
|
295
|
-
if (!
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
);
|
|
386
|
+
const checkedDirectory = options.directory.getVerifiedDirectory(origin);
|
|
387
|
+
if (!checkedDirectory || new URL(checkedDirectory.proof.request.url).origin !== origin)
|
|
388
|
+
denied("directory_origin");
|
|
389
|
+
if (checkedDirectory.expires <= now) denied("directory_expired");
|
|
390
|
+
if (!testKeysAllowed(options.allowTestKeys ?? options.directory.allowsTestKeys) && checkedDirectory.keys.some((key2) => TEST_KIDS.has(key2.kid)))
|
|
391
|
+
denied("directory_key");
|
|
301
392
|
const key = checkedDirectory.keys.find((key2) => key2.kid === keyid);
|
|
302
393
|
if (!key || jwkThumbprint(key) !== keyid) denied("key");
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
try {
|
|
306
|
-
if (await options.isRevoked(origin, keyid)) denied("key_revoked");
|
|
307
|
-
} catch (error) {
|
|
308
|
-
if (options.requireFreshRevocation === false && error instanceof WebBotAuthError && error.reason === "revocation_unavailable") {
|
|
309
|
-
} else if (options.deferAuthorizationErrors && error instanceof WebBotAuthError && ["key_revoked", "revocation_unavailable", "revocation_persistence_failed"].includes(error.reason))
|
|
310
|
-
authorizationFailure = createWebBotAuthAuthorizationFailure(
|
|
311
|
-
error.reason === "revocation_persistence_failed" ? "revocation_unavailable" : error.reason
|
|
312
|
-
);
|
|
313
|
-
else throw error;
|
|
314
|
-
}
|
|
394
|
+
await verifyBytesAsync(input, request, key);
|
|
395
|
+
if (options.isRevoked) await legacyRevocationCheck(options, origin, keyid);
|
|
315
396
|
const nonceTuple = JSON.stringify([origin, nonce]);
|
|
316
397
|
if (nonces.has(nonceTuple)) throw new WebBotAuthError(options.replayStatus ?? 429, "nonce_replayed");
|
|
317
398
|
nonces.add(nonceTuple);
|
|
@@ -320,20 +401,25 @@ async function verifyWebBotAuth(request, options) {
|
|
|
320
401
|
origin,
|
|
321
402
|
keyid,
|
|
322
403
|
label: input.label,
|
|
323
|
-
verified_at: now
|
|
324
|
-
...authorizationFailure && { authorizationFailure }
|
|
404
|
+
verified_at: now
|
|
325
405
|
});
|
|
326
406
|
verified.push(signer);
|
|
327
407
|
}
|
|
328
408
|
for (const signer of verified) {
|
|
329
409
|
const input = selected.find((i) => i.label === signer.label);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
410
|
+
let result;
|
|
411
|
+
try {
|
|
412
|
+
result = await options.replayStore.insert(
|
|
413
|
+
signer.origin,
|
|
414
|
+
WEB_BOT_AUTH_PROFILE,
|
|
415
|
+
input.params.nonce,
|
|
416
|
+
// Acceptance is inclusive of expires + 60, so the nonce must outlive it.
|
|
417
|
+
input.params.expires + 60 - now + 1,
|
|
418
|
+
now
|
|
419
|
+
);
|
|
420
|
+
} catch (cause) {
|
|
421
|
+
throw new WebBotAuthUnavailableError("replay_unavailable", cause);
|
|
422
|
+
}
|
|
337
423
|
if (result !== "ok")
|
|
338
424
|
throw new WebBotAuthError(result === "replayed" ? options.replayStatus ?? 429 : 429, result);
|
|
339
425
|
}
|
|
@@ -342,48 +428,109 @@ async function verifyWebBotAuth(request, options) {
|
|
|
342
428
|
} catch (error) {
|
|
343
429
|
if (error instanceof WebBotAuthError) {
|
|
344
430
|
if (error.headers["Accept-Signature"]) {
|
|
345
|
-
|
|
431
|
+
let label = "sig1";
|
|
432
|
+
try {
|
|
433
|
+
label = inputs(request.headers)[0]?.label ?? "sig1";
|
|
434
|
+
} catch {
|
|
435
|
+
}
|
|
346
436
|
const bodyComponents = request.body ? '"content-type" "content-digest" ' : "";
|
|
347
437
|
error.headers["Accept-Signature"] = `${label}=("@method" "@target-uri" "@authority" ${bodyComponents}"signature-agent";key="${label}");created;expires;nonce;keyid;tag="web-bot-auth"`;
|
|
348
438
|
}
|
|
349
439
|
throw error;
|
|
350
440
|
}
|
|
441
|
+
if (error instanceof WebBotAuthUnavailableError) throw error;
|
|
351
442
|
return denied("verification_unavailable");
|
|
352
443
|
}
|
|
353
444
|
}
|
|
354
|
-
function
|
|
445
|
+
async function legacyRevocationCheck(options, origin, keyid) {
|
|
446
|
+
let revoked;
|
|
447
|
+
try {
|
|
448
|
+
revoked = await options.isRevoked(origin, keyid);
|
|
449
|
+
} catch (error) {
|
|
450
|
+
if (error instanceof WebBotAuthUnavailableError) throw error;
|
|
451
|
+
if (error instanceof WebBotAuthError && error.reason === "revocation_persistence_failed")
|
|
452
|
+
throw new WebBotAuthUnavailableError("revocation_persistence_failed", error);
|
|
453
|
+
if (error instanceof WebBotAuthError && error.reason === "key_revoked") return denied("key_revoked");
|
|
454
|
+
if (options.requireFreshRevocation === false && error instanceof WebBotAuthError && (error.reason === "revocation_unavailable" || error.reason === "revocation_absent"))
|
|
455
|
+
return;
|
|
456
|
+
return denied("identity_unverified");
|
|
457
|
+
}
|
|
458
|
+
if (revoked) denied("key_revoked");
|
|
459
|
+
}
|
|
460
|
+
function testKeysAllowed(allowTestKeys) {
|
|
461
|
+
return allowTestKeys === true && ["test", "development"].includes(process.env.NODE_ENV ?? "");
|
|
462
|
+
}
|
|
463
|
+
const UNSIGNED_DIRECTORY_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
464
|
+
SIGNATURE_DIRECTORY_MEDIA_TYPE,
|
|
465
|
+
"application/jwk-set+json",
|
|
466
|
+
"application/json"
|
|
467
|
+
]);
|
|
468
|
+
function verifyKeyDirectory(response, now = Date.now() / 1e3, options = {}) {
|
|
355
469
|
try {
|
|
356
470
|
if (!Number.isFinite(now)) denied("directory_invalid");
|
|
357
471
|
const origin = canonicalAgentOrigin(response.request.url);
|
|
358
|
-
if (response.request.url !== origin + SIGNATURE_DIRECTORY_PATH || response.status !== 200
|
|
472
|
+
if (response.request.url !== origin + SIGNATURE_DIRECTORY_PATH || response.status !== 200)
|
|
473
|
+
denied("directory_response");
|
|
474
|
+
const signed = getHeaderValue(response.headers, "signature-input") !== void 0;
|
|
475
|
+
if (!signed && options.requireSigned) denied("directory_unsigned");
|
|
476
|
+
const mediaType = getHeaderValue(response.headers, "content-type")?.split(";")[0]?.trim().toLowerCase();
|
|
477
|
+
if (signed ? mediaType !== SIGNATURE_DIRECTORY_MEDIA_TYPE : !UNSIGNED_DIRECTORY_MEDIA_TYPES.has(mediaType ?? ""))
|
|
359
478
|
denied("directory_response");
|
|
360
479
|
const body = response.body ?? "";
|
|
361
480
|
if (Buffer.byteLength(body) > 256 * 1024) denied("directory_size");
|
|
362
|
-
|
|
481
|
+
if (signed || getHeaderValue(response.headers, "content-digest") !== void 0)
|
|
482
|
+
assertDirectoryDigest(response.headers, body);
|
|
363
483
|
const document = parseStrictJson(body, 32);
|
|
364
484
|
if (!Array.isArray(document.keys) || !document.keys.length || document.keys.length > 64) denied("directory_keys");
|
|
365
|
-
const signatures = inputs(response.headers);
|
|
366
|
-
const
|
|
485
|
+
const signatures = signed ? inputs(response.headers) : [];
|
|
486
|
+
const kids = /* @__PURE__ */ new Set();
|
|
487
|
+
const keys = [];
|
|
367
488
|
let expires = Infinity;
|
|
368
|
-
for (const
|
|
369
|
-
if (!
|
|
489
|
+
for (const raw of document.keys) {
|
|
490
|
+
if (!raw || typeof raw !== "object") denied("directory_key");
|
|
491
|
+
const thumbprint = jwkThumbprint(raw);
|
|
492
|
+
const key = { ...raw, kid: raw.kid ?? thumbprint };
|
|
493
|
+
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))
|
|
370
494
|
denied("directory_key");
|
|
371
|
-
|
|
495
|
+
algorithm(key);
|
|
496
|
+
kids.add(key.kid);
|
|
497
|
+
keys.push(key);
|
|
498
|
+
if (!signed) continue;
|
|
372
499
|
const proof = signatures.find((input) => input.params.keyid === key.kid && input.params.tag === DIRECTORY_TAG);
|
|
373
|
-
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 || proof.params.expires <= now || proof.params.expires <= proof.params.created)
|
|
500
|
+
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)
|
|
374
501
|
denied("directory_proof");
|
|
375
502
|
verifyBytes(proof, { ...response.request, headers: response.request.headers ?? {} }, key, response);
|
|
376
503
|
expires = Math.min(expires, proof.params.expires);
|
|
377
504
|
}
|
|
378
|
-
|
|
505
|
+
if (!signed) expires = now + unsignedAge(options.maxUnsignedAgeSeconds);
|
|
506
|
+
return { keys, expires, signed, proof: response };
|
|
379
507
|
} catch (error) {
|
|
380
|
-
if (error instanceof WebBotAuthError)
|
|
508
|
+
if (error instanceof WebBotAuthError) {
|
|
509
|
+
throw error.reason.startsWith("directory_") ? error : new WebBotAuthError(403, `directory_${error.reason}`);
|
|
510
|
+
}
|
|
381
511
|
return denied("directory_invalid");
|
|
382
512
|
}
|
|
383
513
|
}
|
|
514
|
+
function unsignedAge(seconds) {
|
|
515
|
+
const value = seconds ?? 86400;
|
|
516
|
+
if (!Number.isFinite(value) || value < 60)
|
|
517
|
+
throw new TypeError("Unsigned directory age must be a finite number of seconds, at least 60");
|
|
518
|
+
return value;
|
|
519
|
+
}
|
|
520
|
+
function verifySignatureDirectory(response, now = Date.now() / 1e3, allowTestKeys = false) {
|
|
521
|
+
return verifyKeyDirectory(response, now, { allowTestKeys, requireSigned: true });
|
|
522
|
+
}
|
|
523
|
+
function assertDirectoryDigest(headers, body) {
|
|
524
|
+
try {
|
|
525
|
+
assertDigest(headers, body);
|
|
526
|
+
} catch {
|
|
527
|
+
denied("directory_digest");
|
|
528
|
+
}
|
|
529
|
+
}
|
|
384
530
|
class SignatureDirectoryResolver {
|
|
385
531
|
constructor(options = {}) {
|
|
386
532
|
this.options = options;
|
|
533
|
+
unsignedAge(options.maxUnsignedDirectoryAgeSeconds);
|
|
387
534
|
}
|
|
388
535
|
options;
|
|
389
536
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -395,19 +542,22 @@ class SignatureDirectoryResolver {
|
|
|
395
542
|
const origin = receivedAgentOrigin(signerOrigin);
|
|
396
543
|
const now = (this.options.now ?? (() => Date.now() / 1e3))();
|
|
397
544
|
const cached = this.cache.get(origin);
|
|
545
|
+
const usable = () => cached && cached.directory.expires > now ? cached.directory : void 0;
|
|
398
546
|
if (cached && cached.freshUntil > now && cached.directory.expires > now) {
|
|
399
547
|
const key = cached.directory.keys.find((k) => k.kid === keyid);
|
|
400
548
|
if (key || (this.keyMissCooldown.get(origin) ?? 0) > now) return key ?? null;
|
|
401
549
|
this.keyMissCooldown.set(origin, now + 60);
|
|
402
550
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
return denied("directory_unavailable");
|
|
406
|
-
}
|
|
551
|
+
const fromFallback = () => {
|
|
552
|
+
const key = usable()?.keys.find((k) => k.kid === keyid);
|
|
553
|
+
return key ?? denied("directory_unavailable");
|
|
554
|
+
};
|
|
555
|
+
if ((this.failures.get(origin) ?? 0) > now) return fromFallback();
|
|
407
556
|
let pending = this.pending.get(origin);
|
|
408
557
|
if (!pending) {
|
|
409
558
|
if ([...this.pending.keys()].filter((pendingOrigin) => eTldPlusOne(pendingOrigin) === eTldPlusOne(origin)).length >= 2 || this.pending.size >= (this.options.maxConcurrentFetches ?? 16)) {
|
|
410
|
-
|
|
559
|
+
const key = usable()?.keys.find((k) => k.kid === keyid);
|
|
560
|
+
if (key) return key;
|
|
411
561
|
throw new WebBotAuthError(429, "directory_fetch_limit");
|
|
412
562
|
}
|
|
413
563
|
pending = this.fetch(origin, now);
|
|
@@ -416,7 +566,7 @@ class SignatureDirectoryResolver {
|
|
|
416
566
|
try {
|
|
417
567
|
return (await pending).keys.find((k) => k.kid === keyid) ?? null;
|
|
418
568
|
} catch (error) {
|
|
419
|
-
if (
|
|
569
|
+
if (usable()) return fromFallback();
|
|
420
570
|
throw error;
|
|
421
571
|
} finally {
|
|
422
572
|
if (this.pending.get(origin) === pending) this.pending.delete(origin);
|
|
@@ -427,15 +577,22 @@ class SignatureDirectoryResolver {
|
|
|
427
577
|
const url = origin + SIGNATURE_DIRECTORY_PATH;
|
|
428
578
|
const result = await ssrfSafeFetch(url, {
|
|
429
579
|
...this.options.fetchOptions,
|
|
430
|
-
headers: {
|
|
580
|
+
headers: {
|
|
581
|
+
accept: `${SIGNATURE_DIRECTORY_MEDIA_TYPE}, application/jwk-set+json;q=0.9, application/json;q=0.8`
|
|
582
|
+
},
|
|
431
583
|
timeoutMs: 1e4,
|
|
432
584
|
maxBodyBytes: 256 * 1024
|
|
433
585
|
});
|
|
434
|
-
|
|
435
|
-
const
|
|
586
|
+
if (result.status !== 200) denied("directory_unavailable");
|
|
587
|
+
const body = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(result.body);
|
|
588
|
+
const directory = verifyKeyDirectory(
|
|
436
589
|
{ status: result.status, headers: result.headers, body, request: { method: "GET", url } },
|
|
437
590
|
now,
|
|
438
|
-
|
|
591
|
+
{
|
|
592
|
+
allowTestKeys: this.options.allowTestKeys,
|
|
593
|
+
requireSigned: this.options.requireSignedDirectories,
|
|
594
|
+
maxUnsignedAgeSeconds: this.options.maxUnsignedDirectoryAgeSeconds
|
|
595
|
+
}
|
|
439
596
|
);
|
|
440
597
|
const cacheControl = result.headers["cache-control"] ?? "";
|
|
441
598
|
const maxAge = /(?:^|,)\s*(?:no-cache|no-store)(?:,|$)/i.test(cacheControl) ? 0 : Number(cacheControl.match(/(?:^|,)\s*max-age=(\d+)/i)?.[1] ?? 300);
|
|
@@ -460,10 +617,14 @@ class SignatureDirectoryResolver {
|
|
|
460
617
|
}
|
|
461
618
|
}
|
|
462
619
|
get allowsTestKeys() {
|
|
463
|
-
return this.options.allowTestKeys
|
|
620
|
+
return testKeysAllowed(this.options.allowTestKeys);
|
|
621
|
+
}
|
|
622
|
+
/** The directory currently used to verify `origin`, including one kept through a failed refresh. */
|
|
623
|
+
getVerifiedDirectory(origin) {
|
|
624
|
+
return this.cache.get(receivedAgentOrigin(origin))?.directory;
|
|
464
625
|
}
|
|
465
626
|
getAuditProof(origin) {
|
|
466
|
-
return this.
|
|
627
|
+
return this.getVerifiedDirectory(origin)?.proof;
|
|
467
628
|
}
|
|
468
629
|
}
|
|
469
630
|
async function signBase(base, key) {
|
|
@@ -476,8 +637,15 @@ async function signBase(base, key) {
|
|
|
476
637
|
throw new TypeError("Web Bot Auth provider identity does not match its public key");
|
|
477
638
|
return key.provider.sign(Buffer.from(base));
|
|
478
639
|
}
|
|
479
|
-
|
|
480
|
-
|
|
640
|
+
if (alg === "rsa-pss-sha512") {
|
|
641
|
+
const privateKey = createPrivateKey({ key: jwk, format: "jwk" });
|
|
642
|
+
return nodeSign("sha512", Buffer.from(base), {
|
|
643
|
+
key: privateKey,
|
|
644
|
+
padding: cryptoConstants.RSA_PKCS1_PSS_PADDING,
|
|
645
|
+
saltLength: 64
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
return signWithPrivateJwkAsync(alg, jwk, Buffer.from(base));
|
|
481
649
|
}
|
|
482
650
|
async function signWebBotAuth(request, key, options) {
|
|
483
651
|
const origin = canonicalAgentOrigin(options.origin);
|
|
@@ -486,9 +654,15 @@ async function signWebBotAuth(request, key, options) {
|
|
|
486
654
|
const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
|
|
487
655
|
const now = Math.floor((options.now ?? (() => Date.now() / 1e3))());
|
|
488
656
|
const window = options.windowSeconds ?? 300;
|
|
489
|
-
const nonce = options.nonce ?? randomBytes(
|
|
657
|
+
const nonce = options.nonce ?? randomBytes(64).toString("base64url");
|
|
490
658
|
if (!Number.isInteger(window) || window < 1 || window > 300 || !/^[A-Za-z0-9_+\/-]{22,256}={0,2}$/.test(nonce))
|
|
491
659
|
throw new TypeError("Invalid Web Bot Auth lifetime or nonce");
|
|
660
|
+
if (!options.relay && ["signature", "signature-input", "signature-agent"].some(
|
|
661
|
+
(name) => getHeaderValue(request.headers, name) !== void 0
|
|
662
|
+
))
|
|
663
|
+
throw new TypeError(
|
|
664
|
+
"Request already carries signature fields; pass relay: true to add a covering signature instead of replacing them"
|
|
665
|
+
);
|
|
492
666
|
const headers = {};
|
|
493
667
|
for (const [name, value] of Object.entries(request.headers))
|
|
494
668
|
if (value !== void 0 && !["signature", "signature-input", "signature-agent", "content-digest"].includes(name.toLowerCase()))
|
|
@@ -500,6 +674,8 @@ async function signWebBotAuth(request, key, options) {
|
|
|
500
674
|
headers["content-digest"] = computeContentDigest(request.body, "rfc8941-base64");
|
|
501
675
|
components.push('"content-type"', '"content-digest"');
|
|
502
676
|
}
|
|
677
|
+
if (options.relay && ["signature", "signature-input", "signature-agent"].some((name) => !getHeaderValue(request.headers, name)))
|
|
678
|
+
throw new TypeError("Relay requires the existing signature fields");
|
|
503
679
|
const earlier = options.relay ? inputs(request.headers) : [];
|
|
504
680
|
if (earlier.some((input2) => input2.label === label || input2.params.tag !== WEB_BOT_AUTH_PROFILE))
|
|
505
681
|
throw new TypeError("Relay requires existing Web Bot Auth signatures and a fresh label");
|
|
@@ -522,7 +698,10 @@ async function signWebBotAuth(request, key, options) {
|
|
|
522
698
|
headers["signature-input"] = `${priorInput ? priorInput + ", " : ""}${label}=(${components.join(" ")});created=${now};expires=${now + window};nonce="${nonce}";keyid="${jwkThumbprint(jwk)}";alg="${algorithm(jwk)}";tag="web-bot-auth"`;
|
|
523
699
|
const input = parseSignatureInput(headers["signature-input"], label);
|
|
524
700
|
if (priorSignature) headers.signature = priorSignature;
|
|
525
|
-
const bytes = await signBase(
|
|
701
|
+
const bytes = await signBase(
|
|
702
|
+
buildWebBotAuthSignatureBase(input, { ...request, url: new URL(request.url).href, headers }),
|
|
703
|
+
key
|
|
704
|
+
);
|
|
526
705
|
headers.signature = `${priorSignature ? priorSignature + ", " : ""}${label}=:${Buffer.from(bytes).toString("base64")}:`;
|
|
527
706
|
return headers;
|
|
528
707
|
}
|
|
@@ -538,8 +717,7 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
|
|
|
538
717
|
const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
|
|
539
718
|
return {
|
|
540
719
|
kty: jwk.kty,
|
|
541
|
-
crv: jwk.crv,
|
|
542
|
-
x: jwk.x,
|
|
720
|
+
...jwk.kty === "RSA" ? { n: jwk.n, e: jwk.e } : { crv: jwk.crv, x: jwk.x },
|
|
543
721
|
...jwk.y ? { y: jwk.y } : {},
|
|
544
722
|
kid: jwkThumbprint(jwk),
|
|
545
723
|
use: "sig"
|
|
@@ -560,7 +738,7 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
|
|
|
560
738
|
const signatureInputs = [], signatures = [];
|
|
561
739
|
for (let i = 0; i < keys.length; i++) {
|
|
562
740
|
const label = `key${i + 1}`;
|
|
563
|
-
const value = `${label}=("@authority";req "content-digest");created=${now};expires=${now + lifetime};keyid="${keys[i].kid}";alg="${algorithm(keys[i])}";tag="${DIRECTORY_TAG}"`;
|
|
741
|
+
const value = `${label}=("@authority";req "content-digest");created=${now - DIRECTORY_CLOCK_SKEW_SECONDS};expires=${now + lifetime};keyid="${keys[i].kid}";alg="${algorithm(keys[i])}";tag="${DIRECTORY_TAG}"`;
|
|
564
742
|
const input = parseSignatureInput(value, label);
|
|
565
743
|
const bytes = await signBase(
|
|
566
744
|
buildWebBotAuthSignatureBase(input, { method: "GET", url: response.request.url, headers: {} }, response),
|
|
@@ -586,7 +764,7 @@ function createWebBotAuthFetch(upstream, key, options) {
|
|
|
586
764
|
if (init?.body != null) {
|
|
587
765
|
if (typeof init.body === "string") body = init.body;
|
|
588
766
|
else if (init.body instanceof Uint8Array || init.body instanceof ArrayBuffer)
|
|
589
|
-
body = new TextDecoder("utf-8", { fatal: true }).decode(init.body);
|
|
767
|
+
body = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(init.body);
|
|
590
768
|
else throw new TypeError("Web Bot Auth fetch requires a UTF-8 string or byte body");
|
|
591
769
|
}
|
|
592
770
|
const headers = {};
|
|
@@ -606,19 +784,26 @@ export {
|
|
|
606
784
|
WEB_BOT_AUTH_DRAFT_REVISION,
|
|
607
785
|
WEB_BOT_AUTH_PROFILE,
|
|
608
786
|
WebBotAuthError,
|
|
787
|
+
WebBotAuthUnavailableError,
|
|
609
788
|
buildWebBotAuthSignatureBase,
|
|
610
789
|
createSignedSignatureDirectory,
|
|
611
790
|
createWebBotAuthAuthorizationFailure,
|
|
612
791
|
createWebBotAuthFetch,
|
|
792
|
+
deferWebBotAuthPrincipalFailure,
|
|
613
793
|
getWebBotAuthAuthorizationFailure,
|
|
614
794
|
getWebBotAuthLegacyOriginGate,
|
|
615
795
|
isVerifiedWebBotSigner,
|
|
796
|
+
isWebBotAuthRevocationChecked,
|
|
616
797
|
jwkThumbprint,
|
|
617
798
|
receivedAgentOrigin,
|
|
618
799
|
registerWebBotAuthLegacyOriginGate,
|
|
800
|
+
registerWebBotAuthRevocationCheck,
|
|
619
801
|
selectSignatureProfile,
|
|
620
802
|
signWebBotAuth,
|
|
803
|
+
verifyKeyDirectory,
|
|
621
804
|
verifySignatureDirectory,
|
|
622
805
|
verifyWebBotAuth,
|
|
623
|
-
|
|
806
|
+
webBotAuthPrincipalFailureError,
|
|
807
|
+
webBotAuthToolFailure,
|
|
808
|
+
webBotAuthUnavailableResponse
|
|
624
809
|
};
|