@adcp/sdk 16.0.0-beta.1 → 16.0.0-beta.2
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/dist/lib/core/SingleAgentClient.d.mts +10 -1
- package/dist/lib/core/SingleAgentClient.d.ts +10 -1
- package/dist/lib/core/SingleAgentClient.js +29 -8
- package/dist/lib/core/SingleAgentClient.mjs +29 -8
- 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 +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.mjs +2 -0
- package/dist/lib/protocols/index.d.mts +1 -1
- package/dist/lib/protocols/index.d.ts +1 -1
- package/dist/lib/protocols/index.js +2 -2
- package/dist/lib/protocols/index.mjs +2 -2
- 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.js +8 -0
- package/dist/lib/signing/crypto.mjs +9 -1
- 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/verifier.js +1 -1
- package/dist/lib/signing/verifier.mjs +1 -1
- 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 +259 -82
- package/dist/lib/signing/web-bot-auth.mjs +259 -87
- package/dist/lib/signing/webhook-verifier.js +1 -1
- package/dist/lib/signing/webhook-verifier.mjs +1 -1
- 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-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/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 +2 -2
- package/docs/guides/REPORTING-LEDGER.md +15 -5
- package/docs/guides/REPORTING-OPERATIONS.md +17 -11
- 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 +3 -1
|
@@ -12,10 +12,15 @@ import {
|
|
|
12
12
|
webBotAuthToolFailure,
|
|
13
13
|
selectSignatureProfile,
|
|
14
14
|
verifyWebBotAuth,
|
|
15
|
+
isWebBotAuthRevocationChecked,
|
|
16
|
+
webBotAuthUnavailableResponse,
|
|
17
|
+
WebBotAuthUnavailableError,
|
|
18
|
+
deferWebBotAuthPrincipalFailure,
|
|
19
|
+
webBotAuthPrincipalFailureError,
|
|
15
20
|
WebBotAuthError
|
|
16
21
|
} from "../signing/web-bot-auth.mjs";
|
|
17
22
|
import { validateIdentity33Payload } from "../identity-3.3/validation.mjs";
|
|
18
|
-
import { IdentityAuthorizationError } from "../identity-3.3/trust.mjs";
|
|
23
|
+
import { IdentityAuthorizationError, TrustRecordUnavailableError } from "../identity-3.3/trust.mjs";
|
|
19
24
|
import { z } from "zod";
|
|
20
25
|
import {
|
|
21
26
|
COMPATIBLE_ADCP_VERSIONS,
|
|
@@ -1516,6 +1521,10 @@ function buildSignedRequestsPreTransport(signedRequests, effectiveRequestSigning
|
|
|
1516
1521
|
throw new Error(
|
|
1517
1522
|
"Web Bot Auth requires an origin-binding migration fence; use createIdentity33PrincipalResolver or supply assertLegacyOriginAllowed"
|
|
1518
1523
|
);
|
|
1524
|
+
if (signedRequests.webBotAuth && !signedRequests.webBotAuth.isRevoked && !isWebBotAuthRevocationChecked(signedRequests.webBotAuth.resolvePrincipal))
|
|
1525
|
+
throw new Error(
|
|
1526
|
+
"Web Bot Auth requires origin revocation: use createIdentity33PrincipalResolver({ revocation }) or supply isRevoked"
|
|
1527
|
+
);
|
|
1519
1528
|
const requiredFor = effectiveRequestSigning.required_for ?? [];
|
|
1520
1529
|
const verifier = createExpressVerifier({
|
|
1521
1530
|
capability: {
|
|
@@ -1545,20 +1554,25 @@ function buildSignedRequestsPreTransport(signedRequests, effectiveRequestSigning
|
|
|
1545
1554
|
const signers = await verifyWebBotAuth(
|
|
1546
1555
|
{ method: req.method ?? "POST", url: requestTarget, headers: req.headers, body: req.rawBody ?? "" },
|
|
1547
1556
|
{
|
|
1557
|
+
// Identity verification needs only key discovery and replay state;
|
|
1558
|
+
// revocation and binding belong to the principal resolver.
|
|
1548
1559
|
directory: signedRequests.webBotAuth.directory,
|
|
1549
|
-
deferAuthorizationErrors: typeof resolveRequestOperation({ rawBody: req.rawBody, method: req.method, url: req.url }) === "string",
|
|
1550
1560
|
replayStore: signedRequests.replayStore,
|
|
1551
|
-
isRevoked: signedRequests.webBotAuth.isRevoked
|
|
1552
|
-
requireFreshRevocation: ![
|
|
1553
|
-
"get_adcp_capabilities",
|
|
1554
|
-
"get_principal",
|
|
1555
|
-
"list_creative_formats",
|
|
1556
|
-
"list_accounts"
|
|
1557
|
-
].includes(String(resolveRequestOperation({ rawBody: req.rawBody, method: req.method, url: req.url })))
|
|
1561
|
+
...signedRequests.webBotAuth.isRevoked && { isRevoked: signedRequests.webBotAuth.isRevoked }
|
|
1558
1562
|
}
|
|
1559
1563
|
);
|
|
1560
1564
|
const operation = resolveRequestOperation({ rawBody: req.rawBody, method: req.method, url: req.url });
|
|
1561
|
-
|
|
1565
|
+
let principal;
|
|
1566
|
+
let deferredFailure;
|
|
1567
|
+
try {
|
|
1568
|
+
principal = await signedRequests.webBotAuth.resolvePrincipal(signers, operation);
|
|
1569
|
+
} catch (error) {
|
|
1570
|
+
if (error instanceof WebBotAuthError || error instanceof WebBotAuthUnavailableError) throw error;
|
|
1571
|
+
if (typeof operation !== "string") throw webBotAuthPrincipalFailureError(error);
|
|
1572
|
+
const deferred = deferWebBotAuthPrincipalFailure(signers, error);
|
|
1573
|
+
principal = { principal: deferred.principal };
|
|
1574
|
+
deferredFailure = deferred.authorizationFailure;
|
|
1575
|
+
}
|
|
1562
1576
|
const signer = signers[signers.length - 1];
|
|
1563
1577
|
const failed = signers.find((s) => s.authorizationFailure);
|
|
1564
1578
|
const guardedPrincipal = {
|
|
@@ -1567,7 +1581,9 @@ function buildSignedRequestsPreTransport(signedRequests, effectiveRequestSigning
|
|
|
1567
1581
|
...principal.extra,
|
|
1568
1582
|
webBotAuthAuthorization: {
|
|
1569
1583
|
signer: failed ?? signer,
|
|
1570
|
-
...failed && {
|
|
1584
|
+
...(failed?.authorizationFailure ?? deferredFailure) && {
|
|
1585
|
+
authorizationFailure: failed?.authorizationFailure ?? deferredFailure
|
|
1586
|
+
}
|
|
1571
1587
|
}
|
|
1572
1588
|
}
|
|
1573
1589
|
};
|
|
@@ -1578,6 +1594,12 @@ function buildSignedRequestsPreTransport(signedRequests, effectiveRequestSigning
|
|
|
1578
1594
|
});
|
|
1579
1595
|
return false;
|
|
1580
1596
|
} catch (error) {
|
|
1597
|
+
if (error instanceof WebBotAuthUnavailableError) {
|
|
1598
|
+
const outage = webBotAuthUnavailableResponse();
|
|
1599
|
+
res.writeHead(outage.status, { ...outage.headers, "Content-Type": "application/json" });
|
|
1600
|
+
res.end(JSON.stringify(outage.body));
|
|
1601
|
+
return true;
|
|
1602
|
+
}
|
|
1581
1603
|
const rejected = error instanceof WebBotAuthError ? error : new WebBotAuthError(403, "identity_rejected");
|
|
1582
1604
|
res.writeHead(rejected.status, { ...rejected.headers, "Content-Type": "application/json" });
|
|
1583
1605
|
res.end(JSON.stringify(rejected.body));
|
|
@@ -3064,6 +3086,8 @@ function createAdcpServer(config) {
|
|
|
3064
3086
|
try {
|
|
3065
3087
|
await config.identity33.authorize(ctx, toolName, params);
|
|
3066
3088
|
} catch (error) {
|
|
3089
|
+
if (error instanceof TrustRecordUnavailableError)
|
|
3090
|
+
return finalize(adcpError("SERVICE_UNAVAILABLE", { message: error.message, recovery: "transient" }));
|
|
3067
3091
|
if (error instanceof IdentityAuthorizationError)
|
|
3068
3092
|
return finalize(
|
|
3069
3093
|
adcpError("PERMISSION_DENIED", {
|
|
@@ -4687,6 +4711,8 @@ function createAdcpServer(config) {
|
|
|
4687
4711
|
params
|
|
4688
4712
|
);
|
|
4689
4713
|
} catch (error) {
|
|
4714
|
+
if (error instanceof TrustRecordUnavailableError)
|
|
4715
|
+
return adcpError("SERVICE_UNAVAILABLE", { message: error.message, recovery: "transient" });
|
|
4690
4716
|
if (error instanceof IdentityAuthorizationError)
|
|
4691
4717
|
return adcpError("PERMISSION_DENIED", {
|
|
4692
4718
|
message: error.message,
|
|
@@ -47,6 +47,14 @@ function verifySignature(alg, publicKey, data, signature) {
|
|
|
47
47
|
if (alg === "ecdsa-p256-sha256") {
|
|
48
48
|
return (0, import_crypto.verify)("sha256", data, { key: publicKey, dsaEncoding: "ieee-p1363" }, signature);
|
|
49
49
|
}
|
|
50
|
+
if (alg === "rsa-pss-sha512") {
|
|
51
|
+
return (0, import_crypto.verify)(
|
|
52
|
+
"sha512",
|
|
53
|
+
data,
|
|
54
|
+
{ key: publicKey, padding: import_crypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: 64 },
|
|
55
|
+
signature
|
|
56
|
+
);
|
|
57
|
+
}
|
|
50
58
|
return false;
|
|
51
59
|
} catch {
|
|
52
60
|
return false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createPublicKey, verify as nodeVerify } from "crypto";
|
|
1
|
+
import { constants, createPublicKey, verify as nodeVerify } from "crypto";
|
|
2
2
|
import { RequestSignatureError } from "./errors.mjs";
|
|
3
3
|
function jwkToPublicKey(jwk) {
|
|
4
4
|
const publicJwk = { ...jwk };
|
|
@@ -23,6 +23,14 @@ function verifySignature(alg, publicKey, data, signature) {
|
|
|
23
23
|
if (alg === "ecdsa-p256-sha256") {
|
|
24
24
|
return nodeVerify("sha256", data, { key: publicKey, dsaEncoding: "ieee-p1363" }, signature);
|
|
25
25
|
}
|
|
26
|
+
if (alg === "rsa-pss-sha512") {
|
|
27
|
+
return nodeVerify(
|
|
28
|
+
"sha512",
|
|
29
|
+
data,
|
|
30
|
+
{ key: publicKey, padding: constants.RSA_PKCS1_PSS_PADDING, saltLength: 64 },
|
|
31
|
+
signature
|
|
32
|
+
);
|
|
33
|
+
}
|
|
26
34
|
return false;
|
|
27
35
|
} catch {
|
|
28
36
|
return false;
|
|
@@ -82,7 +82,7 @@ function bodyToString(body) {
|
|
|
82
82
|
}
|
|
83
83
|
function decodeUtf8Strict(bytes) {
|
|
84
84
|
try {
|
|
85
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
85
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
86
86
|
} catch {
|
|
87
87
|
throw new TypeError(
|
|
88
88
|
"createSigningFetchAsync received a Uint8Array/ArrayBuffer body that is not valid UTF-8. Pass a string body, ensure the bytes are UTF-8, or sign the request manually with `signRequestAsync` against the exact wire bytes you intend to send."
|
|
@@ -61,7 +61,7 @@ function bodyToString(body) {
|
|
|
61
61
|
}
|
|
62
62
|
function decodeUtf8Strict(bytes) {
|
|
63
63
|
try {
|
|
64
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
64
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
65
65
|
} catch {
|
|
66
66
|
throw new TypeError(
|
|
67
67
|
"createSigningFetchAsync received a Uint8Array/ArrayBuffer body that is not valid UTF-8. Pass a string body, ensure the bytes are UTF-8, or sign the request manually with `signRequestAsync` against the exact wire bytes you intend to send."
|
|
@@ -91,7 +91,7 @@ function bodyToString(body) {
|
|
|
91
91
|
}
|
|
92
92
|
function decodeUtf8Strict(bytes) {
|
|
93
93
|
try {
|
|
94
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
94
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
95
95
|
} catch {
|
|
96
96
|
throw new TypeError(
|
|
97
97
|
"createSigningFetch received a Uint8Array/ArrayBuffer body that is not valid UTF-8. Pass a string body, ensure the bytes are UTF-8, or sign the request manually with `signRequest` against the exact wire bytes you intend to send."
|
|
@@ -67,7 +67,7 @@ function bodyToString(body) {
|
|
|
67
67
|
}
|
|
68
68
|
function decodeUtf8Strict(bytes) {
|
|
69
69
|
try {
|
|
70
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
70
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
71
71
|
} catch {
|
|
72
72
|
throw new TypeError(
|
|
73
73
|
"createSigningFetch received a Uint8Array/ArrayBuffer body that is not valid UTF-8. Pass a string body, ensure the bytes are UTF-8, or sign the request manually with `signRequest` against the exact wire bytes you intend to send."
|
|
@@ -30,6 +30,15 @@ function createExpressVerifier(options) {
|
|
|
30
30
|
const replayStore = options.replayStore ?? new import_replay.InMemoryReplayStore();
|
|
31
31
|
const revocationStore = options.revocationStore ?? new import_revocation.InMemoryRevocationStore();
|
|
32
32
|
return async function requestSignatureMiddleware(req, res, next) {
|
|
33
|
+
const { selectSignatureProfile, WebBotAuthError } = require('./web-bot-auth.js');
|
|
34
|
+
if (selectSignatureProfile({ headers: req.headers }) === "web-bot-auth") {
|
|
35
|
+
const refused = new WebBotAuthError(403, "profile_disabled");
|
|
36
|
+
res.status(refused.status).set("Cache-Control", "no-store").json({
|
|
37
|
+
...refused.body,
|
|
38
|
+
message: "Web Bot Auth is not configured on this endpoint; use createAdcpServer signedRequests.webBotAuth or verifySignatureAsAuthenticator({ webBotAuth })"
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
33
42
|
try {
|
|
34
43
|
const url = options.getUrl ? options.getUrl(req) : defaultUrl(req);
|
|
35
44
|
const body = resolveRawBody(req);
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { fileURLToPath as __adcpFileURLToPath } from "node:url";
|
|
2
|
+
import { dirname as __adcpDirname } from "node:path";
|
|
3
|
+
import { createRequire as __adcpCreateRequire } from "node:module";
|
|
4
|
+
const __dirname = __adcpDirname(__adcpFileURLToPath(import.meta.url));
|
|
5
|
+
const require2 = __adcpCreateRequire(import.meta.url);
|
|
1
6
|
import { getHeaderValue } from "./canonicalize.mjs";
|
|
2
7
|
import { RequestSignatureError } from "./errors.mjs";
|
|
3
8
|
import { InMemoryReplayStore } from "./replay.mjs";
|
|
@@ -7,6 +12,15 @@ function createExpressVerifier(options) {
|
|
|
7
12
|
const replayStore = options.replayStore ?? new InMemoryReplayStore();
|
|
8
13
|
const revocationStore = options.revocationStore ?? new InMemoryRevocationStore();
|
|
9
14
|
return async function requestSignatureMiddleware(req, res, next) {
|
|
15
|
+
const { selectSignatureProfile, WebBotAuthError } = require2("./web-bot-auth");
|
|
16
|
+
if (selectSignatureProfile({ headers: req.headers }) === "web-bot-auth") {
|
|
17
|
+
const refused = new WebBotAuthError(403, "profile_disabled");
|
|
18
|
+
res.status(refused.status).set("Cache-Control", "no-store").json({
|
|
19
|
+
...refused.body,
|
|
20
|
+
message: "Web Bot Auth is not configured on this endpoint; use createAdcpServer signedRequests.webBotAuth or verifySignatureAsAuthenticator({ webBotAuth })"
|
|
21
|
+
});
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
10
24
|
try {
|
|
11
25
|
const url = options.getUrl ? options.getUrl(req) : defaultUrl(req);
|
|
12
26
|
const body = resolveRawBody(req);
|
|
@@ -26,10 +26,13 @@ export interface ParsedSignature {
|
|
|
26
26
|
export declare function parseSignatureInput(headerValue: string, expectedLabel?: string): ParsedSignatureInput;
|
|
27
27
|
export declare function parseSignature(headerValue: string, expectedLabel: string, encoding?: SfBinaryEncoding): ParsedSignature;
|
|
28
28
|
/**
|
|
29
|
-
* RFC 8941
|
|
30
|
-
* profile (step 1) overrides that:
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
29
|
+
* RFC 8941 / RFC 9651 parsers deduplicate dictionary keys and parameter keys
|
|
30
|
+
* by keeping the last value. The AdCP profile (step 1) overrides that:
|
|
31
|
+
* duplicates are a cross-implementation differential and a downgrade vector
|
|
32
|
+
* (a proxy could smuggle a weaker component set or a different `created`,
|
|
33
|
+
* `keyid` or `tag` past a verifier that reads the first). Detect them at the
|
|
34
|
+
* raw-header level before the library silently drops them: top-level
|
|
35
|
+
* dictionary keys, parameters on members and inner lists, and parameters on
|
|
36
|
+
* each inner-list item.
|
|
34
37
|
*/
|
|
35
38
|
export declare function rejectDuplicateDictionaryKeys(headerValue: string, headerName: string): void;
|
|
@@ -26,10 +26,13 @@ export interface ParsedSignature {
|
|
|
26
26
|
export declare function parseSignatureInput(headerValue: string, expectedLabel?: string): ParsedSignatureInput;
|
|
27
27
|
export declare function parseSignature(headerValue: string, expectedLabel: string, encoding?: SfBinaryEncoding): ParsedSignature;
|
|
28
28
|
/**
|
|
29
|
-
* RFC 8941
|
|
30
|
-
* profile (step 1) overrides that:
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
29
|
+
* RFC 8941 / RFC 9651 parsers deduplicate dictionary keys and parameter keys
|
|
30
|
+
* by keeping the last value. The AdCP profile (step 1) overrides that:
|
|
31
|
+
* duplicates are a cross-implementation differential and a downgrade vector
|
|
32
|
+
* (a proxy could smuggle a weaker component set or a different `created`,
|
|
33
|
+
* `keyid` or `tag` past a verifier that reads the first). Detect them at the
|
|
34
|
+
* raw-header level before the library silently drops them: top-level
|
|
35
|
+
* dictionary keys, parameters on members and inner lists, and parameters on
|
|
36
|
+
* each inner-list item.
|
|
34
37
|
*/
|
|
35
38
|
export declare function rejectDuplicateDictionaryKeys(headerValue: string, headerName: string): void;
|
|
@@ -111,85 +111,101 @@ function isInnerList(entry) {
|
|
|
111
111
|
return Array.isArray(entry) && Array.isArray(entry[0]);
|
|
112
112
|
}
|
|
113
113
|
function rejectDuplicateDictionaryKeys(headerValue, headerName) {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (seen.has(key)) {
|
|
118
|
-
malformed(`${headerName} header declares key "${key}" more than once`);
|
|
119
|
-
}
|
|
120
|
-
seen.add(key);
|
|
121
|
-
}
|
|
114
|
+
const duplicate = findDuplicateStructuredKey(headerValue);
|
|
115
|
+
if (duplicate?.kind === "member") malformed(`${headerName} header declares key "${duplicate.key}" more than once`);
|
|
116
|
+
if (duplicate) malformed(`${headerName} header declares parameter "${duplicate.key}" more than once`);
|
|
122
117
|
}
|
|
123
|
-
function
|
|
124
|
-
const keys = [];
|
|
118
|
+
function findDuplicateStructuredKey(input) {
|
|
125
119
|
let i = 0;
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
120
|
+
const n = input.length;
|
|
121
|
+
const ows = () => {
|
|
122
|
+
while (i < n && (input[i] === " " || input[i] === " ")) i++;
|
|
123
|
+
};
|
|
124
|
+
const key = () => {
|
|
125
|
+
const start = i;
|
|
126
|
+
while (i < n && /[A-Za-z0-9_.*-]/.test(input[i])) i++;
|
|
127
|
+
return input.slice(start, i).toLowerCase();
|
|
128
|
+
};
|
|
129
|
+
const bareItem = () => {
|
|
130
|
+
if (input[i] === "%" && input[i + 1] === '"') {
|
|
131
|
+
i += 2;
|
|
132
|
+
while (i < n && input[i] !== '"') i++;
|
|
133
|
+
i++;
|
|
134
|
+
return;
|
|
136
135
|
}
|
|
137
|
-
|
|
138
|
-
if (ch === '"') {
|
|
136
|
+
if (input[i] === '"') {
|
|
139
137
|
i++;
|
|
140
|
-
while (i <
|
|
141
|
-
if (input[i] === "\\"
|
|
138
|
+
while (i < n) {
|
|
139
|
+
if (input[i] === "\\") {
|
|
142
140
|
i += 2;
|
|
143
141
|
continue;
|
|
144
142
|
}
|
|
145
|
-
if (input[i] === '"')
|
|
146
|
-
i++;
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
i++;
|
|
143
|
+
if (input[i++] === '"') return;
|
|
150
144
|
}
|
|
151
|
-
|
|
145
|
+
return;
|
|
152
146
|
}
|
|
153
|
-
if (
|
|
147
|
+
if (input[i] === ":") {
|
|
154
148
|
i++;
|
|
155
|
-
while (i <
|
|
156
|
-
|
|
157
|
-
|
|
149
|
+
while (i < n && input[i] !== ":") i++;
|
|
150
|
+
i++;
|
|
151
|
+
return;
|
|
158
152
|
}
|
|
159
|
-
|
|
153
|
+
while (i < n && !/[;,()\s"]/.test(input[i])) i++;
|
|
154
|
+
};
|
|
155
|
+
const parameters = () => {
|
|
156
|
+
const seen = /* @__PURE__ */ new Set();
|
|
157
|
+
while (i < n && input[i] === ";") {
|
|
160
158
|
i++;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (input[i] === "\\" && i + 1 < len) {
|
|
168
|
-
i += 2;
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
if (input[i] === '"') {
|
|
172
|
-
i++;
|
|
173
|
-
break;
|
|
174
|
-
}
|
|
175
|
-
i++;
|
|
176
|
-
}
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
if (c === "(") depth++;
|
|
180
|
-
else if (c === ")") depth--;
|
|
159
|
+
ows();
|
|
160
|
+
const k = key();
|
|
161
|
+
if (!k) return void 0;
|
|
162
|
+
if (seen.has(k)) return k;
|
|
163
|
+
seen.add(k);
|
|
164
|
+
if (input[i] === "=") {
|
|
181
165
|
i++;
|
|
166
|
+
bareItem();
|
|
182
167
|
}
|
|
183
|
-
continue;
|
|
184
168
|
}
|
|
185
|
-
|
|
186
|
-
|
|
169
|
+
return void 0;
|
|
170
|
+
};
|
|
171
|
+
const members = /* @__PURE__ */ new Set();
|
|
172
|
+
while (i < n) {
|
|
173
|
+
ows();
|
|
174
|
+
const k = key();
|
|
175
|
+
if (!k) return void 0;
|
|
176
|
+
if (members.has(k)) return { kind: "member", key: k };
|
|
177
|
+
members.add(k);
|
|
178
|
+
let duplicate;
|
|
179
|
+
if (input[i] === "=") {
|
|
187
180
|
i++;
|
|
188
|
-
|
|
189
|
-
|
|
181
|
+
if (input[i] === "(") {
|
|
182
|
+
i++;
|
|
183
|
+
for (; ; ) {
|
|
184
|
+
while (i < n && input[i] === " ") i++;
|
|
185
|
+
if (i >= n) return void 0;
|
|
186
|
+
if (input[i] === ")") {
|
|
187
|
+
i++;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
const before = i;
|
|
191
|
+
bareItem();
|
|
192
|
+
duplicate = parameters();
|
|
193
|
+
if (duplicate) return { kind: "parameter", key: `${k}: ${duplicate}` };
|
|
194
|
+
if (i === before) return void 0;
|
|
195
|
+
}
|
|
196
|
+
duplicate = parameters();
|
|
197
|
+
} else {
|
|
198
|
+
bareItem();
|
|
199
|
+
duplicate = parameters();
|
|
200
|
+
}
|
|
201
|
+
} else duplicate = parameters();
|
|
202
|
+
if (duplicate) return { kind: "parameter", key: `${k}: ${duplicate}` };
|
|
203
|
+
ows();
|
|
204
|
+
if (i >= n) return void 0;
|
|
205
|
+
if (input[i] !== ",") return void 0;
|
|
190
206
|
i++;
|
|
191
207
|
}
|
|
192
|
-
return
|
|
208
|
+
return void 0;
|
|
193
209
|
}
|
|
194
210
|
function normalizeByteSequenceBase64(input) {
|
|
195
211
|
let out = "";
|
|
@@ -86,85 +86,101 @@ function isInnerList(entry) {
|
|
|
86
86
|
return Array.isArray(entry) && Array.isArray(entry[0]);
|
|
87
87
|
}
|
|
88
88
|
function rejectDuplicateDictionaryKeys(headerValue, headerName) {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (seen.has(key)) {
|
|
93
|
-
malformed(`${headerName} header declares key "${key}" more than once`);
|
|
94
|
-
}
|
|
95
|
-
seen.add(key);
|
|
96
|
-
}
|
|
89
|
+
const duplicate = findDuplicateStructuredKey(headerValue);
|
|
90
|
+
if (duplicate?.kind === "member") malformed(`${headerName} header declares key "${duplicate.key}" more than once`);
|
|
91
|
+
if (duplicate) malformed(`${headerName} header declares parameter "${duplicate.key}" more than once`);
|
|
97
92
|
}
|
|
98
|
-
function
|
|
99
|
-
const keys = [];
|
|
93
|
+
function findDuplicateStructuredKey(input) {
|
|
100
94
|
let i = 0;
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
95
|
+
const n = input.length;
|
|
96
|
+
const ows = () => {
|
|
97
|
+
while (i < n && (input[i] === " " || input[i] === " ")) i++;
|
|
98
|
+
};
|
|
99
|
+
const key = () => {
|
|
100
|
+
const start = i;
|
|
101
|
+
while (i < n && /[A-Za-z0-9_.*-]/.test(input[i])) i++;
|
|
102
|
+
return input.slice(start, i).toLowerCase();
|
|
103
|
+
};
|
|
104
|
+
const bareItem = () => {
|
|
105
|
+
if (input[i] === "%" && input[i + 1] === '"') {
|
|
106
|
+
i += 2;
|
|
107
|
+
while (i < n && input[i] !== '"') i++;
|
|
108
|
+
i++;
|
|
109
|
+
return;
|
|
111
110
|
}
|
|
112
|
-
|
|
113
|
-
if (ch === '"') {
|
|
111
|
+
if (input[i] === '"') {
|
|
114
112
|
i++;
|
|
115
|
-
while (i <
|
|
116
|
-
if (input[i] === "\\"
|
|
113
|
+
while (i < n) {
|
|
114
|
+
if (input[i] === "\\") {
|
|
117
115
|
i += 2;
|
|
118
116
|
continue;
|
|
119
117
|
}
|
|
120
|
-
if (input[i] === '"')
|
|
121
|
-
i++;
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
i++;
|
|
118
|
+
if (input[i++] === '"') return;
|
|
125
119
|
}
|
|
126
|
-
|
|
120
|
+
return;
|
|
127
121
|
}
|
|
128
|
-
if (
|
|
122
|
+
if (input[i] === ":") {
|
|
129
123
|
i++;
|
|
130
|
-
while (i <
|
|
131
|
-
|
|
132
|
-
|
|
124
|
+
while (i < n && input[i] !== ":") i++;
|
|
125
|
+
i++;
|
|
126
|
+
return;
|
|
133
127
|
}
|
|
134
|
-
|
|
128
|
+
while (i < n && !/[;,()\s"]/.test(input[i])) i++;
|
|
129
|
+
};
|
|
130
|
+
const parameters = () => {
|
|
131
|
+
const seen = /* @__PURE__ */ new Set();
|
|
132
|
+
while (i < n && input[i] === ";") {
|
|
135
133
|
i++;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (input[i] === "\\" && i + 1 < len) {
|
|
143
|
-
i += 2;
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
if (input[i] === '"') {
|
|
147
|
-
i++;
|
|
148
|
-
break;
|
|
149
|
-
}
|
|
150
|
-
i++;
|
|
151
|
-
}
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
if (c === "(") depth++;
|
|
155
|
-
else if (c === ")") depth--;
|
|
134
|
+
ows();
|
|
135
|
+
const k = key();
|
|
136
|
+
if (!k) return void 0;
|
|
137
|
+
if (seen.has(k)) return k;
|
|
138
|
+
seen.add(k);
|
|
139
|
+
if (input[i] === "=") {
|
|
156
140
|
i++;
|
|
141
|
+
bareItem();
|
|
157
142
|
}
|
|
158
|
-
continue;
|
|
159
143
|
}
|
|
160
|
-
|
|
161
|
-
|
|
144
|
+
return void 0;
|
|
145
|
+
};
|
|
146
|
+
const members = /* @__PURE__ */ new Set();
|
|
147
|
+
while (i < n) {
|
|
148
|
+
ows();
|
|
149
|
+
const k = key();
|
|
150
|
+
if (!k) return void 0;
|
|
151
|
+
if (members.has(k)) return { kind: "member", key: k };
|
|
152
|
+
members.add(k);
|
|
153
|
+
let duplicate;
|
|
154
|
+
if (input[i] === "=") {
|
|
162
155
|
i++;
|
|
163
|
-
|
|
164
|
-
|
|
156
|
+
if (input[i] === "(") {
|
|
157
|
+
i++;
|
|
158
|
+
for (; ; ) {
|
|
159
|
+
while (i < n && input[i] === " ") i++;
|
|
160
|
+
if (i >= n) return void 0;
|
|
161
|
+
if (input[i] === ")") {
|
|
162
|
+
i++;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
const before = i;
|
|
166
|
+
bareItem();
|
|
167
|
+
duplicate = parameters();
|
|
168
|
+
if (duplicate) return { kind: "parameter", key: `${k}: ${duplicate}` };
|
|
169
|
+
if (i === before) return void 0;
|
|
170
|
+
}
|
|
171
|
+
duplicate = parameters();
|
|
172
|
+
} else {
|
|
173
|
+
bareItem();
|
|
174
|
+
duplicate = parameters();
|
|
175
|
+
}
|
|
176
|
+
} else duplicate = parameters();
|
|
177
|
+
if (duplicate) return { kind: "parameter", key: `${k}: ${duplicate}` };
|
|
178
|
+
ows();
|
|
179
|
+
if (i >= n) return void 0;
|
|
180
|
+
if (input[i] !== ",") return void 0;
|
|
165
181
|
i++;
|
|
166
182
|
}
|
|
167
|
-
return
|
|
183
|
+
return void 0;
|
|
168
184
|
}
|
|
169
185
|
function normalizeByteSequenceBase64(input) {
|
|
170
186
|
let out = "";
|
|
@@ -59,6 +59,9 @@ const REASONS = [
|
|
|
59
59
|
];
|
|
60
60
|
function mergeRevocationDetails(prior, detail) {
|
|
61
61
|
const reason = REASONS.indexOf(detail.reason ?? "unspecified") > REASONS.indexOf(prior?.reason ?? "unspecified") ? detail.reason : prior?.reason ?? detail.reason;
|
|
62
|
+
const hasValidDate = (entry) => !!entry?.invalidity_date && Number.isFinite(Date.parse(entry.invalidity_date));
|
|
63
|
+
if (reason === "keyCompromise" && [prior, detail].some((entry) => entry?.reason === "keyCompromise" && !hasValidDate(entry)))
|
|
64
|
+
return { reason };
|
|
62
65
|
const dates = [prior?.invalidity_date, detail.invalidity_date].filter((s) => !!s && Number.isFinite(Date.parse(s))).sort((a, b) => Date.parse(a) - Date.parse(b));
|
|
63
66
|
return { ...reason ? { reason } : {}, ...dates[0] ? { invalidity_date: dates[0] } : {} };
|
|
64
67
|
}
|
|
@@ -35,6 +35,9 @@ const REASONS = [
|
|
|
35
35
|
];
|
|
36
36
|
function mergeRevocationDetails(prior, detail) {
|
|
37
37
|
const reason = REASONS.indexOf(detail.reason ?? "unspecified") > REASONS.indexOf(prior?.reason ?? "unspecified") ? detail.reason : prior?.reason ?? detail.reason;
|
|
38
|
+
const hasValidDate = (entry) => !!entry?.invalidity_date && Number.isFinite(Date.parse(entry.invalidity_date));
|
|
39
|
+
if (reason === "keyCompromise" && [prior, detail].some((entry) => entry?.reason === "keyCompromise" && !hasValidDate(entry)))
|
|
40
|
+
return { reason };
|
|
38
41
|
const dates = [prior?.invalidity_date, detail.invalidity_date].filter((s) => !!s && Number.isFinite(Date.parse(s))).sort((a, b) => Date.parse(a) - Date.parse(b));
|
|
39
42
|
return { ...reason ? { reason } : {}, ...dates[0] ? { invalidity_date: dates[0] } : {} };
|
|
40
43
|
}
|
|
@@ -239,7 +239,7 @@ async function verifyRequestSignature(request, options) {
|
|
|
239
239
|
"Delegated operator authorization expired during verification"
|
|
240
240
|
);
|
|
241
241
|
}
|
|
242
|
-
const remaining = parsedInput.params.expires - now + import_types.CLOCK_SKEW_TOLERANCE_SECONDS;
|
|
242
|
+
const remaining = parsedInput.params.expires - now + import_types.CLOCK_SKEW_TOLERANCE_SECONDS + 1;
|
|
243
243
|
const ttl = Math.max(remaining, import_types.MAX_SIGNATURE_WINDOW_SECONDS + import_types.CLOCK_SKEW_TOLERANCE_SECONDS);
|
|
244
244
|
const insertResult = await options.replayStore.insert(jwk.kid, replayScope, parsedInput.params.nonce, ttl, now);
|
|
245
245
|
if (insertResult === "replayed") {
|
|
@@ -228,7 +228,7 @@ async function verifyRequestSignature(request, options) {
|
|
|
228
228
|
"Delegated operator authorization expired during verification"
|
|
229
229
|
);
|
|
230
230
|
}
|
|
231
|
-
const remaining = parsedInput.params.expires - now + CLOCK_SKEW_TOLERANCE_SECONDS;
|
|
231
|
+
const remaining = parsedInput.params.expires - now + CLOCK_SKEW_TOLERANCE_SECONDS + 1;
|
|
232
232
|
const ttl = Math.max(remaining, MAX_SIGNATURE_WINDOW_SECONDS + CLOCK_SKEW_TOLERANCE_SECONDS);
|
|
233
233
|
const insertResult = await options.replayStore.insert(jwk.kid, replayScope, parsedInput.params.nonce, ttl, now);
|
|
234
234
|
if (insertResult === "replayed") {
|