@request-seal/core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +341 -114
  3. package/dist/accept-signature.d.ts +11 -0
  4. package/dist/accept-signature.js +7 -0
  5. package/dist/adapters/fetch/index.d.ts +10 -0
  6. package/dist/adapters/fetch/index.js +21 -4
  7. package/dist/adapters/fetch/runtime.d.ts +1 -0
  8. package/dist/adapters/fetch/runtime.js +1 -0
  9. package/dist/adapters/node-http/index.d.ts +16 -0
  10. package/dist/adapters/node-http/index.js +8 -4
  11. package/dist/brand.d.ts +5 -0
  12. package/dist/brand.js +12 -0
  13. package/dist/bytes.d.ts +4 -0
  14. package/dist/bytes.js +5 -0
  15. package/dist/constants.d.ts +61 -1
  16. package/dist/constants.js +61 -1
  17. package/dist/context.d.ts +4 -0
  18. package/dist/context.js +3 -0
  19. package/dist/crypto/algorithm.d.ts +5 -0
  20. package/dist/crypto/index.d.ts +12 -2
  21. package/dist/crypto/index.js +34 -11
  22. package/dist/crypto/options.d.ts +3 -0
  23. package/dist/crypto/options.js +15 -0
  24. package/dist/crypto/primitive.d.ts +7 -3
  25. package/dist/crypto/primitive.js +36 -10
  26. package/dist/custody/content_key.d.ts +4 -0
  27. package/dist/custody/content_key.js +4 -2
  28. package/dist/custody/index.d.ts +2 -0
  29. package/dist/custody/index.js +5 -1
  30. package/dist/custody/local.d.ts +17 -1
  31. package/dist/custody/local.js +10 -5
  32. package/dist/custody/ssh-agent/index.d.ts +2 -0
  33. package/dist/custody/ssh-agent/index.js +11 -181
  34. package/dist/custody/ssh-agent/port.d.ts +19 -0
  35. package/dist/custody/ssh-agent/port.js +183 -0
  36. package/dist/digest.d.ts +22 -0
  37. package/dist/digest.js +15 -0
  38. package/dist/discovery/body.d.ts +7 -0
  39. package/dist/discovery/body.js +3 -0
  40. package/dist/discovery/cache.d.ts +9 -0
  41. package/dist/discovery/cache.js +2 -0
  42. package/dist/discovery/headers.d.ts +1 -0
  43. package/dist/discovery/index.d.ts +5 -0
  44. package/dist/discovery/index.js +10 -2
  45. package/dist/discovery/node-connection.d.ts +1 -1
  46. package/dist/discovery/node-connection.js +2 -2
  47. package/dist/discovery/node-trust.d.ts +2 -0
  48. package/dist/discovery/node-trust.js +9 -0
  49. package/dist/discovery/node.d.ts +4 -0
  50. package/dist/discovery/node.js +29 -11
  51. package/dist/discovery/source.d.ts +7 -0
  52. package/dist/discovery/source.js +5 -0
  53. package/dist/discovery/support.js +2 -3
  54. package/dist/errors/parity.d.ts +16 -1
  55. package/dist/errors/parity.js +14 -2
  56. package/dist/errors/types.d.ts +13 -1
  57. package/dist/errors/types.js +2 -0
  58. package/dist/index.d.ts +1 -0
  59. package/dist/index.js +1 -0
  60. package/dist/jose/header.d.ts +1 -0
  61. package/dist/jose/jwe.d.ts +9 -0
  62. package/dist/jose/jwe.js +17 -3
  63. package/dist/jose/jws.d.ts +11 -0
  64. package/dist/jose/jws.js +14 -2
  65. package/dist/jose/key-management.d.ts +11 -0
  66. package/dist/jose/key-management.js +13 -2
  67. package/dist/jose/nested.d.ts +6 -0
  68. package/dist/jose/nested.js +9 -1
  69. package/dist/json.d.ts +7 -0
  70. package/dist/key_handle.d.ts +12 -3
  71. package/dist/key_handle.js +32 -25
  72. package/dist/key_identity.d.ts +7 -0
  73. package/dist/key_identity.js +10 -2
  74. package/dist/message/index.d.ts +24 -0
  75. package/dist/message/index.js +20 -0
  76. package/dist/numbers.d.ts +6 -0
  77. package/dist/numbers.js +5 -0
  78. package/dist/policy.d.ts +15 -0
  79. package/dist/policy.js +30 -21
  80. package/dist/profile.d.ts +36 -0
  81. package/dist/profile.js +215 -0
  82. package/dist/public_key.d.ts +11 -2
  83. package/dist/public_key.js +31 -8
  84. package/dist/quorum/verification.d.ts +2 -0
  85. package/dist/quorum/verification.js +1 -0
  86. package/dist/quorum.d.ts +9 -0
  87. package/dist/quorum.js +7 -1
  88. package/dist/replay/claim.d.ts +5 -0
  89. package/dist/replay/claim.js +9 -1
  90. package/dist/replay/index.d.ts +6 -0
  91. package/dist/replay/index.js +13 -1
  92. package/dist/replay/postgres.d.ts +1 -0
  93. package/dist/replay/postgres.js +1 -0
  94. package/dist/replay/receipt.d.ts +1 -0
  95. package/dist/replay/receipt.js +1 -0
  96. package/dist/replay/store.d.ts +1 -0
  97. package/dist/replay/store.js +1 -0
  98. package/dist/replay-port.d.ts +6 -1
  99. package/dist/result.d.ts +3 -0
  100. package/dist/result.js +2 -0
  101. package/dist/sign.d.ts +5 -0
  102. package/dist/sign.js +1 -0
  103. package/dist/signature-base/index.d.ts +2 -0
  104. package/dist/signature-base/index.js +5 -1
  105. package/dist/structured-fields/index.d.ts +21 -0
  106. package/dist/structured-fields/index.js +14 -1
  107. package/dist/verification.d.ts +5 -1
  108. package/dist/verification.js +9 -6
  109. package/dist/verify-quorum.d.ts +1 -0
  110. package/dist/verify-quorum.js +1 -0
  111. package/dist/verify.d.ts +5 -0
  112. package/dist/verify.js +16 -11
  113. package/dist/web-bot-auth/index.d.ts +3 -0
  114. package/dist/web-bot-auth/index.js +3 -0
  115. package/dist/web-bot-auth/policy.d.ts +8 -0
  116. package/dist/web-bot-auth/policy.js +8 -4
  117. package/dist/web-bot-auth/verification.d.ts +8 -0
  118. package/dist/web-bot-auth/verification.js +4 -2
  119. package/package.json +17 -6
package/dist/verify.js CHANGED
@@ -50,6 +50,14 @@ async function freshness(params, policy, context) {
50
50
  skew: policy.skew,
51
51
  });
52
52
  }
53
+ /** Internal coverage/freshness pipeline shared by selected verification and preflight. */
54
+ export async function authenticationPreflight(input, components, extra, freshnessPolicy, context) {
55
+ const covered = new Set(identities(input)), needed = new Set(identities(components));
56
+ ensure([...needed].every((id) => covered.has(id)), "missing_required_component", "policy");
57
+ ensure(extra === "allow" ||
58
+ [...covered].every((id) => needed.has(id)), "unexpected_component", "policy");
59
+ return freshness(parameters(input), typeof freshnessPolicy === "function" ? freshnessPolicy() : freshnessPolicy, context);
60
+ }
53
61
  async function content(m, input, policy, options) {
54
62
  if (policy === "not_required")
55
63
  return policy;
@@ -90,7 +98,7 @@ async function content(m, input, policy, options) {
90
98
  }
91
99
  return checked.value;
92
100
  }
93
- async function replay(params, algorithm, fresh, policy, context) {
101
+ async function replay(params, algorithm, fresh, policy, context, profile) {
94
102
  if (policy === "not_required")
95
103
  return policy;
96
104
  ensure(typeof params.nonce === "string" && params.nonce.length > 0, "missing_replay_identifier", "replay");
@@ -112,7 +120,7 @@ async function replay(params, algorithm, fresh, policy, context) {
112
120
  tag: (params.tag ?? null),
113
121
  created: f.created,
114
122
  expires: f.expires,
115
- profile: Object.freeze({ name: "rfc9421" }),
123
+ profile,
116
124
  });
117
125
  return commitReplay(facts, retainUntil, policy, context);
118
126
  }
@@ -172,7 +180,7 @@ export async function commitReplay(facts, retainUntil, policy, context) {
172
180
  releaseContext(child.value);
173
181
  }
174
182
  }
175
- async function verifyLabel(message, policy, options, observeIdentity, parsed) {
183
+ export async function verifyLabel(message, policy, options, observeIdentity, parsed, profile = Object.freeze({ name: "rfc9421" })) {
176
184
  try {
177
185
  // Reserved stamps have their own bounded reason even when other options reject.
178
186
  ensure(options !== null && typeof options === "object" && !Array.isArray(options), "invalid_options", "input");
@@ -222,11 +230,7 @@ async function verifyLabel(message, policy, options, observeIdentity, parsed) {
222
230
  ensure(params.nonce === undefined ||
223
231
  (typeof params.nonce === "string" &&
224
232
  params.nonce.length <= SIGNATURE_MAX_BYTES), "limit", "input");
225
- const covered = new Set(identities(input)), needed = new Set(identities(policy.components));
226
- ensure([...needed].every((id) => covered.has(id)), "missing_required_component", "policy");
227
- ensure(policy.extraComponents === "allow" ||
228
- [...covered].every((id) => needed.has(id)), "unexpected_component", "policy");
229
- const fresh = await freshness(params, policy.freshness, c.value);
233
+ const fresh = await authenticationPreflight(input, policy.components, policy.extraComponents, policy.freshness, c.value);
230
234
  ensure(!Object.hasOwn(params, "alg") ||
231
235
  policy.algorithms.some(a => typeof a === "string" && a === params.alg), "algorithm_not_permitted", "policy");
232
236
  const resolution = await run(c.value, async (context) => {
@@ -336,7 +340,7 @@ async function verifyLabel(message, policy, options, observeIdentity, parsed) {
336
340
  throw contentResult.error;
337
341
  fail("digest_unsupported", "content");
338
342
  }
339
- const replayFacts = await replay(params, algorithm, fresh, policy.replay, c.value);
343
+ const replayFacts = await replay(params, algorithm, fresh, policy.replay, c.value, profile);
340
344
  let identity;
341
345
  if (observeIdentity) {
342
346
  const id = await run(c.value, async () => {
@@ -364,7 +368,7 @@ async function verifyLabel(message, policy, options, observeIdentity, parsed) {
364
368
  keyid: (params.keyid ?? null),
365
369
  crypto: "valid",
366
370
  },
367
- profile: { name: "rfc9421" }, content: contentResult.value, freshness: fresh,
371
+ profile, content: contentResult.value, freshness: fresh,
368
372
  replay: replayFacts, principal: "unattributed", authorization: "not_evaluated",
369
373
  });
370
374
  if (result.ok && identity)
@@ -374,9 +378,10 @@ async function verifyLabel(message, policy, options, observeIdentity, parsed) {
374
378
  catch (e) {
375
379
  if (isRequestSealError(e) && e.kind === "custody" && e.reason === "deadline_exceeded")
376
380
  return err(e);
377
- return err(bounded(e, "invalid_options", "input"));
381
+ return err(bounded(e, typeof profile.name === "string" ? "invalid_options" : "invalid_policy", "input"));
378
382
  }
379
383
  }
384
+ /** Verify one HTTP signature under an explicit branded policy. */
380
385
  export function verify(message, policy, options) {
381
386
  return verifyLabel(message, policy, options);
382
387
  }
@@ -1,10 +1,13 @@
1
1
  import { verifyWebBotAuth } from "./verify.ts";
2
2
  import { signWebBotAuth } from "./sign.ts";
3
+ /** Web Bot Auth policy construction, signing, and verification operations. */
3
4
  export declare const WebBotAuth: Readonly<{
4
5
  Policy: Readonly<{
5
6
  create: typeof import("./policy.ts").createPolicy;
6
7
  }>;
8
+ /** Verify an agent signature envelope under explicit trust, freshness, and replay rules. */
7
9
  verify: typeof verifyWebBotAuth;
10
+ /** Add a Web Bot Auth signature using explicit agent and freshness parameters. */
8
11
  sign: typeof signWebBotAuth;
9
12
  }>;
10
13
  export { isWebBotAuthVerification } from "./verification.ts";
@@ -1,9 +1,12 @@
1
1
  import { WebBotAuthPolicy } from "./policy.js";
2
2
  import { verifyWebBotAuth } from "./verify.js";
3
3
  import { signWebBotAuth } from "./sign.js";
4
+ /** Web Bot Auth policy construction, signing, and verification operations. */
4
5
  export const WebBotAuth = Object.freeze({
5
6
  Policy: WebBotAuthPolicy,
7
+ /** Verify an agent signature envelope under explicit trust, freshness, and replay rules. */
6
8
  verify: verifyWebBotAuth,
9
+ /** Add a Web Bot Auth signature using explicit agent and freshness parameters. */
7
10
  sign: signWebBotAuth,
8
11
  });
9
12
  export { isWebBotAuthVerification } from "./verification.js";
@@ -5,15 +5,20 @@ import type { KeySet } from "../discovery/body.ts";
5
5
  import type { Context } from "../context.ts";
6
6
  import type { Result } from "../result.ts";
7
7
  import type { WebBotAuthReplayFacts } from "./verification.ts";
8
+ /** Agent location, type, and deadline context passed to a trust callback. */
8
9
  export type AgentRequest = Readonly<{
9
10
  identifier: string;
10
11
  location: string;
11
12
  type: DiscoverySource["type"];
12
13
  }>;
14
+ /** Resolve a trusted discovery key set for an agent request. */
13
15
  export type AgentTrust = (agent: AgentRequest) => Result<DiscoverySource | KeySet | null> | Promise<Result<DiscoverySource | KeySet | null>>;
16
+ /** Replay rules with a commitment callback over the verified signature envelope. */
14
17
  export type WebBotAuthReplayPolicy = Omit<ReplayPolicy, "commitment"> & Readonly<{
18
+ /** Derive caller-owned replay commitment bytes from verified facts. */
15
19
  commitment: (facts: WebBotAuthReplayFacts, context: Context) => Result<Uint8Array> | Promise<Result<Uint8Array>>;
16
20
  }>;
21
+ /** A branded agent-trust policy for Web Bot Auth verification. */
17
22
  export type WebBotAuthPolicy = Omit<GenericPolicy, "keyResolver" | "replay" | "freshness"> & Readonly<{
18
23
  agents: AgentTrust;
19
24
  cache: DiscoveryCache | null;
@@ -26,8 +31,11 @@ export type WebBotAuthPolicy = Omit<GenericPolicy, "keyResolver" | "replay" | "f
26
31
  testKeys: "reject" | "allow";
27
32
  untagged: "reject" | "ignore";
28
33
  }>;
34
+ /** Check the private brand of a Web Bot Auth policy. */
29
35
  export declare function isWebBotAuthPolicy(v: unknown): v is WebBotAuthPolicy;
30
36
  export declare function createPolicy(input: unknown): Result<WebBotAuthPolicy>;
37
+ /** A branded agent-trust policy for Web Bot Auth verification. */
31
38
  export declare const WebBotAuthPolicy: Readonly<{
39
+ /** Snapshot, validate, and brand a Web Bot Auth trust policy. */
32
40
  create: typeof createPolicy;
33
41
  }>;
@@ -1,10 +1,11 @@
1
- import { snapshotOptions } from "../brand.js";
1
+ import { inspection, snapshotOptions } from "../brand.js";
2
2
  import { Policy, } from "../policy.js";
3
3
  import { isDiscoveryCache } from "../discovery/cache.js";
4
4
  import { ok, err } from "../result.js";
5
5
  import { ensure, bounded } from "../signature-fields.js";
6
6
  import { resolveAlgorithm } from "../crypto/algorithm.js";
7
7
  const policies = new WeakSet();
8
+ /** Check the private brand of a Web Bot Auth policy. */
8
9
  export function isWebBotAuthPolicy(v) {
9
10
  return !!v && typeof v === "object" && policies.has(v);
10
11
  }
@@ -92,8 +93,7 @@ export function createPolicy(input) {
92
93
  });
93
94
  Object.defineProperties(p, {
94
95
  toJSON: { value: inspect },
95
- [Symbol.for("nodejs.util.inspect.custom")]: { value: inspect },
96
- [Symbol.for("Deno.customInspect")]: { value: inspect },
96
+ ...inspection(inspect),
97
97
  });
98
98
  Object.freeze(p);
99
99
  policies.add(p);
@@ -103,4 +103,8 @@ export function createPolicy(input) {
103
103
  return err(bounded(e, "invalid_policy", "input"));
104
104
  }
105
105
  }
106
- export const WebBotAuthPolicy = Object.freeze({ create: createPolicy });
106
+ /** A branded agent-trust policy for Web Bot Auth verification. */
107
+ export const WebBotAuthPolicy = Object.freeze({
108
+ /** Snapshot, validate, and brand a Web Bot Auth trust policy. */
109
+ create: createPolicy
110
+ });
@@ -2,11 +2,14 @@ import type { Verification } from "../verification.ts";
2
2
  import type { ReplayReceipt } from "../replay-port.ts";
3
3
  import type { SigningAlgorithm } from "../crypto/algorithm.ts";
4
4
  import type { Proof } from "../discovery/body.ts";
5
+ /** Pinned Web Bot Auth profile metadata. */
5
6
  export declare const WEB_BOT_AUTH_PROFILE: Readonly<{
6
7
  name: "web_bot_auth";
7
8
  revision: "draft-ietf-webbotauth-httpsig-protocol-00";
8
9
  }>;
10
+ /** Profile name `web_bot_auth` and revision `draft-ietf-webbotauth-httpsig-protocol-00`. */
9
11
  export type WebBotAuthProfile = typeof WEB_BOT_AUTH_PROFILE;
12
+ /** A verified agent identity with discovery provenance and key thumbprint. */
10
13
  export type AgentPrincipal = Readonly<{
11
14
  kind: "agent";
12
15
  identifier: string;
@@ -20,14 +23,17 @@ export type AgentPrincipal = Readonly<{
20
23
  revision: string | null;
21
24
  }>;
22
25
  }>;
26
+ /** A verified held-key principal identified by its thumbprint. */
23
27
  export type HeldPrincipal = Readonly<{
24
28
  kind: "key";
25
29
  thumbprint: string;
26
30
  }>;
31
+ /** Single-signature verification facts narrowed to Web Bot Auth principals. */
27
32
  export type WebBotAuthSignature = Omit<Verification, "profile" | "principal"> & Readonly<{
28
33
  profile: WebBotAuthProfile;
29
34
  principal: AgentPrincipal | HeldPrincipal;
30
35
  }>;
36
+ /** Verified Web Bot Auth envelope facts supplied to a replay commitment callback. */
31
37
  export type WebBotAuthReplayFacts = Readonly<{
32
38
  profile: WebBotAuthProfile;
33
39
  signatures: readonly Readonly<{
@@ -40,6 +46,7 @@ export type WebBotAuthReplayFacts = Readonly<{
40
46
  expires: number;
41
47
  }>[];
42
48
  }>;
49
+ /** A branded Web Bot Auth envelope with per-label facts, evidence, and replay status. */
43
50
  export type WebBotAuthVerification = Readonly<{
44
51
  profile: WebBotAuthProfile;
45
52
  signatures: Readonly<Record<string, WebBotAuthSignature>>;
@@ -49,6 +56,7 @@ export type WebBotAuthVerification = Readonly<{
49
56
  authorization: "not_evaluated";
50
57
  }>;
51
58
  import { type Result } from "../result.ts";
59
+ /** Check the private brand of a Web Bot Auth verification envelope. */
52
60
  export declare function isWebBotAuthVerification(v: unknown): v is WebBotAuthVerification;
53
61
  /** Internal constructor; single-label facts retain their independent brands. */
54
62
  export declare function completedEnvelope(signatures: Record<string, WebBotAuthSignature>, evidence: Record<string, readonly string[]>, ignored: readonly string[], replay: WebBotAuthVerification["replay"]): Result<WebBotAuthVerification>;
@@ -1,3 +1,5 @@
1
+ import { inspection } from "../brand.js";
2
+ /** Pinned Web Bot Auth profile metadata. */
1
3
  export const WEB_BOT_AUTH_PROFILE = Object.freeze({
2
4
  name: "web_bot_auth",
3
5
  revision: "draft-ietf-webbotauth-httpsig-protocol-00",
@@ -7,6 +9,7 @@ import { coreError } from "../errors/types.js";
7
9
  import { verificationFacts, isVerification } from "../verification.js";
8
10
  import { isReplayReceipt } from "../replay/receipt.js";
9
11
  const envelopes = new WeakSet();
12
+ /** Check the private brand of a Web Bot Auth verification envelope. */
10
13
  export function isWebBotAuthVerification(v) {
11
14
  return !!v && typeof v === "object" && envelopes.has(v);
12
15
  }
@@ -31,8 +34,7 @@ export function completedEnvelope(signatures, evidence, ignored, replay) {
31
34
  });
32
35
  Object.defineProperties(result, {
33
36
  toJSON: { value: inspect },
34
- [Symbol.for("nodejs.util.inspect.custom")]: { value: inspect },
35
- [Symbol.for("Deno.customInspect")]: { value: inspect },
37
+ ...inspection(inspect),
36
38
  });
37
39
  envelopes.add(result);
38
40
  return ok(Object.freeze(result));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@request-seal/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Standards-based HTTP message signatures and JOSE for web-standard runtimes.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -61,19 +61,30 @@
61
61
  "url": "https://github.com/baselabs/request-seal-typescript/issues"
62
62
  },
63
63
  "scripts": {
64
- "typecheck": "tsc -p tsconfig.json",
65
- "typecheck:web": "tsc -p tsconfig.web.json",
64
+ "typecheck": "node node_modules/typescript/bin/tsc -p tsconfig.json",
65
+ "typecheck:web": "node node_modules/typescript/bin/tsc -p tsconfig.web.json",
66
66
  "test": "node --unhandled-rejections=strict --experimental-strip-types --no-warnings --test-concurrency=4 --test test/*.test.ts",
67
67
  "test:red": "node --unhandled-rejections=strict --experimental-strip-types --no-warnings --test test/red/planted.test.ts",
68
68
  "self-checks": "node --experimental-strip-types --no-warnings self-checks.ts",
69
- "build": "node --experimental-strip-types --no-warnings scripts/clean-dist.ts && tsc -p tsconfig.build.json",
69
+ "build": "node --experimental-strip-types --no-warnings scripts/clean-dist.ts && node node_modules/typescript/bin/tsc -p tsconfig.build.json",
70
70
  "conformance": "node --experimental-strip-types --no-warnings tools/conformance/cli.ts --corpus test/fixtures/corpus-smoke --smoke && node --experimental-strip-types --no-warnings tools/conformance/cli.ts --corpus conformance --pending-ok",
71
71
  "pack:check": "node --experimental-strip-types --no-warnings scripts/pack-check.ts",
72
- "check": "npm run typecheck && npm run typecheck:web && npm test && npm run self-checks && npm run build && npm run conformance && npm run pack:check"
72
+ "check": "npm run typecheck && npm run typecheck:web && npm run build && npm test && npm run self-checks && npm run conformance && npm run pack:check",
73
+ "docs": "node --import ./scripts/typedoc-register.mjs node_modules/typedoc/bin/typedoc --options typedoc.json"
73
74
  },
74
75
  "devDependencies": {
76
+ "@types/markdown-it": "14.1.2",
75
77
  "@types/node": "26.6.4",
76
78
  "fast-check": "4.10.2",
77
- "typescript": "7.0.2"
79
+ "github-slugger": "2.0.0",
80
+ "markdown-it": "14.3.2",
81
+ "typedoc": "0.28.20",
82
+ "typescript": "7.0.2",
83
+ "typescript-docs": "npm:typescript@6.0.3"
84
+ },
85
+ "overrides": {
86
+ "typedoc": {
87
+ "typescript": "$typescript"
88
+ }
78
89
  }
79
90
  }