@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.
Files changed (97) hide show
  1. package/dist/lib/core/SingleAgentClient.d.mts +10 -1
  2. package/dist/lib/core/SingleAgentClient.d.ts +10 -1
  3. package/dist/lib/core/SingleAgentClient.js +29 -8
  4. package/dist/lib/core/SingleAgentClient.mjs +29 -8
  5. package/dist/lib/identity-3.3/generated-compat.d.mts +34 -0
  6. package/dist/lib/identity-3.3/generated-compat.d.ts +34 -0
  7. package/dist/lib/identity-3.3/generated-compat.js +16 -0
  8. package/dist/lib/identity-3.3/generated-compat.mjs +0 -0
  9. package/dist/lib/identity-3.3/index.d.mts +2 -2
  10. package/dist/lib/identity-3.3/index.d.ts +2 -2
  11. package/dist/lib/identity-3.3/index.js +4 -0
  12. package/dist/lib/identity-3.3/index.mjs +5 -1
  13. package/dist/lib/identity-3.3/lifecycle.d.mts +17 -5
  14. package/dist/lib/identity-3.3/lifecycle.d.ts +17 -5
  15. package/dist/lib/identity-3.3/lifecycle.js +32 -5
  16. package/dist/lib/identity-3.3/lifecycle.mjs +38 -6
  17. package/dist/lib/identity-3.3/revocation.d.mts +39 -3
  18. package/dist/lib/identity-3.3/revocation.d.ts +39 -3
  19. package/dist/lib/identity-3.3/revocation.js +88 -18
  20. package/dist/lib/identity-3.3/revocation.mjs +89 -19
  21. package/dist/lib/identity-3.3/server.d.mts +12 -0
  22. package/dist/lib/identity-3.3/server.d.ts +12 -0
  23. package/dist/lib/identity-3.3/server.js +103 -43
  24. package/dist/lib/identity-3.3/server.mjs +111 -45
  25. package/dist/lib/identity-3.3/trust.d.mts +62 -1
  26. package/dist/lib/identity-3.3/trust.d.ts +62 -1
  27. package/dist/lib/identity-3.3/trust.js +126 -21
  28. package/dist/lib/identity-3.3/trust.mjs +124 -21
  29. package/dist/lib/identity-3.3/types.d.mts +39 -11
  30. package/dist/lib/identity-3.3/types.d.ts +39 -11
  31. package/dist/lib/index.d.mts +1 -1
  32. package/dist/lib/index.d.ts +1 -1
  33. package/dist/lib/index.js +2 -0
  34. package/dist/lib/index.mjs +2 -0
  35. package/dist/lib/protocols/index.d.mts +1 -1
  36. package/dist/lib/protocols/index.d.ts +1 -1
  37. package/dist/lib/protocols/index.js +2 -2
  38. package/dist/lib/protocols/index.mjs +2 -2
  39. package/dist/lib/reporting/bigquery/index.js +8 -0
  40. package/dist/lib/reporting/bigquery/index.mjs +8 -0
  41. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  42. package/dist/lib/server/auth-signature.d.mts +6 -1
  43. package/dist/lib/server/auth-signature.d.ts +6 -1
  44. package/dist/lib/server/auth-signature.js +24 -13
  45. package/dist/lib/server/auth-signature.mjs +29 -13
  46. package/dist/lib/server/auth.d.mts +2 -2
  47. package/dist/lib/server/auth.d.ts +2 -2
  48. package/dist/lib/server/create-adcp-server.js +31 -10
  49. package/dist/lib/server/create-adcp-server.mjs +37 -11
  50. package/dist/lib/signing/crypto.js +8 -0
  51. package/dist/lib/signing/crypto.mjs +9 -1
  52. package/dist/lib/signing/fetch-async.js +1 -1
  53. package/dist/lib/signing/fetch-async.mjs +1 -1
  54. package/dist/lib/signing/fetch.js +1 -1
  55. package/dist/lib/signing/fetch.mjs +1 -1
  56. package/dist/lib/signing/middleware.js +9 -0
  57. package/dist/lib/signing/middleware.mjs +14 -0
  58. package/dist/lib/signing/parser.d.mts +8 -5
  59. package/dist/lib/signing/parser.d.ts +8 -5
  60. package/dist/lib/signing/parser.js +77 -61
  61. package/dist/lib/signing/parser.mjs +77 -61
  62. package/dist/lib/signing/revocation.js +3 -0
  63. package/dist/lib/signing/revocation.mjs +3 -0
  64. package/dist/lib/signing/verifier.js +1 -1
  65. package/dist/lib/signing/verifier.mjs +1 -1
  66. package/dist/lib/signing/web-bot-auth.d.mts +84 -10
  67. package/dist/lib/signing/web-bot-auth.d.ts +84 -10
  68. package/dist/lib/signing/web-bot-auth.js +259 -82
  69. package/dist/lib/signing/web-bot-auth.mjs +259 -87
  70. package/dist/lib/signing/webhook-verifier.js +1 -1
  71. package/dist/lib/signing/webhook-verifier.mjs +1 -1
  72. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  73. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  74. package/dist/lib/v2/projection/catalog-snapshot.js +3 -1
  75. package/dist/lib/v2/projection/catalog-snapshot.mjs +3 -1
  76. package/dist/lib/v2/projection/creative-delivery.d.mts +31 -9
  77. package/dist/lib/v2/projection/creative-delivery.d.ts +31 -9
  78. package/dist/lib/v2/projection/index.d.mts +1 -1
  79. package/dist/lib/v2/projection/index.d.ts +1 -1
  80. package/dist/lib/v2/projection/index.js +2 -0
  81. package/dist/lib/v2/projection/index.mjs +2 -0
  82. package/dist/lib/v2/projection/legacy-routes.d.mts +10 -0
  83. package/dist/lib/v2/projection/legacy-routes.d.ts +10 -0
  84. package/dist/lib/v2/projection/legacy-routes.js +7 -0
  85. package/dist/lib/v2/projection/legacy-routes.mjs +6 -0
  86. package/dist/lib/version.d.mts +3 -3
  87. package/dist/lib/version.d.ts +3 -3
  88. package/dist/lib/version.js +3 -3
  89. package/dist/lib/version.mjs +3 -3
  90. package/docs/TYPE-SUMMARY.md +2 -2
  91. package/docs/guides/REPORTING-LEDGER.md +15 -5
  92. package/docs/guides/REPORTING-OPERATIONS.md +17 -11
  93. package/docs/llms.txt +2 -2
  94. package/docs/migration-12-to-13.md +8 -0
  95. package/docs/migration-14.x-rc-worksheet.md +4 -4
  96. package/docs/migration-agent-identity-3.3.md +150 -11
  97. 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
- const principal = await signedRequests.webBotAuth.resolvePrincipal(signers, operation);
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 && { authorizationFailure: failed.authorizationFailure }
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 §3.2 parsers deduplicate by keeping the last value. The AdCP
30
- * profile (step 1) overrides that: duplicate keys are a downgrade vector —
31
- * a proxy could smuggle a weaker component set past a verifier that reads
32
- * the first. Detect duplicates at the raw-header level before the library
33
- * silently drops them.
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 §3.2 parsers deduplicate by keeping the last value. The AdCP
30
- * profile (step 1) overrides that: duplicate keys are a downgrade vector —
31
- * a proxy could smuggle a weaker component set past a verifier that reads
32
- * the first. Detect duplicates at the raw-header level before the library
33
- * silently drops them.
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 keys = extractTopLevelDictKeys(headerValue);
115
- const seen = /* @__PURE__ */ new Set();
116
- for (const key of keys) {
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 extractTopLevelDictKeys(input) {
124
- const keys = [];
118
+ function findDuplicateStructuredKey(input) {
125
119
  let i = 0;
126
- const len = input.length;
127
- let atEntryStart = true;
128
- while (i < len) {
129
- if (atEntryStart) {
130
- while (i < len && (input[i] === " " || input[i] === " ")) i++;
131
- const keyStart = i;
132
- while (i < len && /[A-Za-z0-9_*-]/.test(input[i])) i++;
133
- if (i > keyStart) keys.push(input.slice(keyStart, i).toLowerCase());
134
- atEntryStart = false;
135
- continue;
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
- const ch = input[i];
138
- if (ch === '"') {
136
+ if (input[i] === '"') {
139
137
  i++;
140
- while (i < len) {
141
- if (input[i] === "\\" && i + 1 < len) {
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
- continue;
145
+ return;
152
146
  }
153
- if (ch === ":") {
147
+ if (input[i] === ":") {
154
148
  i++;
155
- while (i < len && input[i] !== ":") i++;
156
- if (i < len) i++;
157
- continue;
149
+ while (i < n && input[i] !== ":") i++;
150
+ i++;
151
+ return;
158
152
  }
159
- if (ch === "(") {
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
- let depth = 1;
162
- while (i < len && depth > 0) {
163
- const c = input[i];
164
- if (c === '"') {
165
- i++;
166
- while (i < len) {
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
- if (ch === ",") {
186
- atEntryStart = true;
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
- continue;
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 keys;
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 keys = extractTopLevelDictKeys(headerValue);
90
- const seen = /* @__PURE__ */ new Set();
91
- for (const key of keys) {
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 extractTopLevelDictKeys(input) {
99
- const keys = [];
93
+ function findDuplicateStructuredKey(input) {
100
94
  let i = 0;
101
- const len = input.length;
102
- let atEntryStart = true;
103
- while (i < len) {
104
- if (atEntryStart) {
105
- while (i < len && (input[i] === " " || input[i] === " ")) i++;
106
- const keyStart = i;
107
- while (i < len && /[A-Za-z0-9_*-]/.test(input[i])) i++;
108
- if (i > keyStart) keys.push(input.slice(keyStart, i).toLowerCase());
109
- atEntryStart = false;
110
- continue;
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
- const ch = input[i];
113
- if (ch === '"') {
111
+ if (input[i] === '"') {
114
112
  i++;
115
- while (i < len) {
116
- if (input[i] === "\\" && i + 1 < len) {
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
- continue;
120
+ return;
127
121
  }
128
- if (ch === ":") {
122
+ if (input[i] === ":") {
129
123
  i++;
130
- while (i < len && input[i] !== ":") i++;
131
- if (i < len) i++;
132
- continue;
124
+ while (i < n && input[i] !== ":") i++;
125
+ i++;
126
+ return;
133
127
  }
134
- if (ch === "(") {
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
- let depth = 1;
137
- while (i < len && depth > 0) {
138
- const c = input[i];
139
- if (c === '"') {
140
- i++;
141
- while (i < len) {
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
- if (ch === ",") {
161
- atEntryStart = true;
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
- continue;
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 keys;
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") {