@request-seal/core 0.1.0 → 0.3.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 (128) hide show
  1. package/CHANGELOG.md +49 -1
  2. package/README.md +342 -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 +23 -0
  10. package/dist/adapters/node-http/index.js +28 -8
  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 +64 -4
  16. package/dist/constants.js +62 -2
  17. package/dist/context.d.ts +4 -0
  18. package/dist/context.js +3 -0
  19. package/dist/crypto/algorithm.d.ts +9 -0
  20. package/dist/crypto/algorithm.js +12 -0
  21. package/dist/crypto/index.d.ts +12 -2
  22. package/dist/crypto/index.js +34 -11
  23. package/dist/crypto/options.d.ts +3 -0
  24. package/dist/crypto/options.js +15 -0
  25. package/dist/crypto/primitive.d.ts +7 -3
  26. package/dist/crypto/primitive.js +36 -10
  27. package/dist/custody/content_key.d.ts +4 -0
  28. package/dist/custody/content_key.js +4 -2
  29. package/dist/custody/index.d.ts +2 -0
  30. package/dist/custody/index.js +5 -1
  31. package/dist/custody/local.d.ts +17 -1
  32. package/dist/custody/local.js +10 -5
  33. package/dist/custody/ssh-agent/index.d.ts +2 -0
  34. package/dist/custody/ssh-agent/index.js +11 -181
  35. package/dist/custody/ssh-agent/port.d.ts +19 -0
  36. package/dist/custody/ssh-agent/port.js +183 -0
  37. package/dist/digest.d.ts +22 -0
  38. package/dist/digest.js +15 -0
  39. package/dist/discovery/body.d.ts +19 -2
  40. package/dist/discovery/body.js +36 -11
  41. package/dist/discovery/cache.d.ts +21 -2
  42. package/dist/discovery/cache.js +105 -39
  43. package/dist/discovery/headers.d.ts +3 -0
  44. package/dist/discovery/headers.js +27 -1
  45. package/dist/discovery/index.d.ts +13 -1
  46. package/dist/discovery/index.js +31 -7
  47. package/dist/discovery/node-connection.d.ts +1 -1
  48. package/dist/discovery/node-connection.js +2 -2
  49. package/dist/discovery/node-trust.d.ts +2 -0
  50. package/dist/discovery/node-trust.js +9 -0
  51. package/dist/discovery/node.d.ts +9 -1
  52. package/dist/discovery/node.js +150 -26
  53. package/dist/discovery/proof.js +2 -2
  54. package/dist/discovery/snapshot.js +3 -0
  55. package/dist/discovery/source.d.ts +7 -0
  56. package/dist/discovery/source.js +5 -0
  57. package/dist/discovery/support.js +2 -3
  58. package/dist/errors/parity.d.ts +44 -3
  59. package/dist/errors/parity.js +45 -4
  60. package/dist/errors/types.d.ts +17 -3
  61. package/dist/errors/types.js +16 -2
  62. package/dist/index.d.ts +1 -0
  63. package/dist/index.js +1 -0
  64. package/dist/jose/header.d.ts +1 -0
  65. package/dist/jose/jwe.d.ts +9 -0
  66. package/dist/jose/jwe.js +17 -3
  67. package/dist/jose/jws.d.ts +11 -0
  68. package/dist/jose/jws.js +14 -2
  69. package/dist/jose/key-management.d.ts +11 -0
  70. package/dist/jose/key-management.js +13 -2
  71. package/dist/jose/nested.d.ts +6 -0
  72. package/dist/jose/nested.js +32 -2
  73. package/dist/json.d.ts +7 -0
  74. package/dist/key_handle.d.ts +12 -3
  75. package/dist/key_handle.js +32 -25
  76. package/dist/key_identity.d.ts +7 -0
  77. package/dist/key_identity.js +10 -2
  78. package/dist/message/index.d.ts +24 -0
  79. package/dist/message/index.js +22 -1
  80. package/dist/numbers.d.ts +6 -0
  81. package/dist/numbers.js +5 -0
  82. package/dist/policy.d.ts +27 -0
  83. package/dist/policy.js +42 -21
  84. package/dist/profile.d.ts +36 -0
  85. package/dist/profile.js +215 -0
  86. package/dist/public_key.d.ts +11 -2
  87. package/dist/public_key.js +31 -8
  88. package/dist/quorum/evaluation.d.ts +1 -1
  89. package/dist/quorum/evaluation.js +18 -4
  90. package/dist/quorum/verification.d.ts +2 -0
  91. package/dist/quorum/verification.js +1 -0
  92. package/dist/quorum.d.ts +9 -0
  93. package/dist/quorum.js +7 -1
  94. package/dist/replay/claim.d.ts +5 -0
  95. package/dist/replay/claim.js +9 -1
  96. package/dist/replay/index.d.ts +6 -0
  97. package/dist/replay/index.js +13 -1
  98. package/dist/replay/postgres.d.ts +3 -0
  99. package/dist/replay/postgres.js +7 -0
  100. package/dist/replay/receipt.d.ts +1 -0
  101. package/dist/replay/receipt.js +1 -0
  102. package/dist/replay/store.d.ts +1 -0
  103. package/dist/replay/store.js +1 -0
  104. package/dist/replay-port.d.ts +6 -1
  105. package/dist/result.d.ts +3 -0
  106. package/dist/result.js +2 -0
  107. package/dist/sign.d.ts +26 -0
  108. package/dist/sign.js +25 -3
  109. package/dist/signature-base/index.d.ts +2 -0
  110. package/dist/signature-base/index.js +5 -1
  111. package/dist/signature-fields.d.ts +5 -1
  112. package/dist/signature-fields.js +9 -1
  113. package/dist/structured-fields/index.d.ts +21 -0
  114. package/dist/structured-fields/index.js +14 -1
  115. package/dist/verification.d.ts +5 -1
  116. package/dist/verification.js +9 -6
  117. package/dist/verify-quorum.d.ts +1 -0
  118. package/dist/verify-quorum.js +30 -8
  119. package/dist/verify.d.ts +23 -0
  120. package/dist/verify.js +46 -14
  121. package/dist/web-bot-auth/index.d.ts +3 -0
  122. package/dist/web-bot-auth/index.js +3 -0
  123. package/dist/web-bot-auth/policy.d.ts +8 -0
  124. package/dist/web-bot-auth/policy.js +8 -4
  125. package/dist/web-bot-auth/verification.d.ts +8 -0
  126. package/dist/web-bot-auth/verification.js +4 -2
  127. package/dist/web-bot-auth/verify.js +2 -2
  128. package/package.json +29 -6
@@ -1,185 +1,11 @@
1
- import { connect } from "node:net";
2
1
  import { snapshotOptions } from "../../brand.js";
3
- import { withDeadline, isContext, run } from "../../context.js";
4
- import { custodyError, isRequestSealError } from "../../errors/types.js";
5
- import { createKeyHandle, } from "../../key_handle.js";
6
- import { KeyIdentity } from "../../key_identity.js";
7
- import { isPublicKey, bindPublicKey, verifyPublicKey, } from "../../public_key.js";
8
- import { resolveAlgorithm, selection, } from "../../crypto/algorithm.js";
2
+ import { withDeadline, isContext } from "../../context.js";
3
+ import { custodyError } from "../../errors/types.js";
4
+ import { createKeyHandle } from "../../key_handle.js";
5
+ import { isPublicKey, bindPublicKey } from "../../public_key.js";
6
+ import { resolveAlgorithm, selection } from "../../crypto/algorithm.js";
9
7
  import { ok, err } from "../../result.js";
10
- import { MAX_FRAME, uint32, string, identities, signature, keyBlob, } from "./wire.js";
11
- function failure(e) {
12
- return err(isRequestSealError(e) && e.kind === "custody"
13
- ? e
14
- : custodyError("custodian_protocol"));
15
- }
16
- class AgentPort {
17
- #path;
18
- #public;
19
- #spec;
20
- #blob;
21
- #live = true;
22
- #sockets = new Set();
23
- constructor(path, publicKey, spec) {
24
- this.#path = path;
25
- this.#public = publicKey;
26
- this.#spec = spec;
27
- this.#blob = keyBlob(publicKey);
28
- Object.freeze(this);
29
- }
30
- async #connect(c) {
31
- return new Promise((resolve) => {
32
- const s = connect(this.#path);
33
- this.#sockets.add(s);
34
- let settled = false;
35
- const done = (r) => {
36
- if (settled)
37
- return;
38
- settled = true;
39
- s.removeListener("error", error);
40
- c.signal.removeEventListener("abort", abort);
41
- resolve(r);
42
- };
43
- const abort = () => {
44
- s.destroy();
45
- done(err(custodyError("deadline_exceeded")));
46
- };
47
- const error = (e) => {
48
- s.destroy();
49
- done(err(custodyError(["ENOENT", "ECONNREFUSED", "ECONNRESET"].includes(e.code ?? "")
50
- ? "custodian_unavailable"
51
- : "custodian_failure")));
52
- };
53
- s.once("close", () => this.#sockets.delete(s));
54
- s.once("error", error);
55
- s.once("connect", () => done(ok(s)));
56
- c.signal.addEventListener("abort", abort, { once: true });
57
- if (c.signal.aborted)
58
- abort();
59
- });
60
- }
61
- async #exchange(socket, request, c) {
62
- return new Promise((resolve) => {
63
- let buffer = Buffer.alloc(0), settled = false;
64
- const done = (r) => {
65
- if (settled)
66
- return;
67
- settled = true;
68
- socket.removeListener("data", data);
69
- socket.removeListener("error", error);
70
- socket.removeListener("end", error);
71
- socket.removeListener("close", error);
72
- c.signal.removeEventListener("abort", abort);
73
- resolve(r);
74
- };
75
- const error = () => done(err(custodyError("custodian_protocol")));
76
- const abort = () => {
77
- socket.destroy();
78
- done(err(custodyError("deadline_exceeded")));
79
- };
80
- const data = (chunk) => {
81
- if (buffer.length + chunk.length > MAX_FRAME + 4) {
82
- socket.destroy();
83
- error();
84
- return;
85
- }
86
- buffer = Buffer.concat([buffer, chunk]);
87
- if (buffer.length < 4)
88
- return;
89
- const length = buffer.readUInt32BE(0);
90
- if (length < 1 || length > MAX_FRAME || buffer.length > length + 4) {
91
- socket.destroy();
92
- error();
93
- return;
94
- }
95
- if (buffer.length === length + 4)
96
- done(ok(new Uint8Array(buffer.subarray(4))));
97
- };
98
- socket.on("data", data);
99
- socket.once("error", error);
100
- socket.once("end", error);
101
- socket.once("close", error);
102
- c.signal.addEventListener("abort", abort, { once: true });
103
- if (c.signal.aborted) {
104
- abort();
105
- return;
106
- }
107
- if (request.length > MAX_FRAME) {
108
- done(err(custodyError("limit")));
109
- return;
110
- }
111
- socket.write(Buffer.concat([uint32(request.length), request]), (e) => {
112
- if (e)
113
- error();
114
- });
115
- });
116
- }
117
- async possession(c, initial = false) {
118
- return this.#sign(new Uint8Array(), c, initial);
119
- }
120
- async #sign(data, c, initial = false) {
121
- return run(c, async () => {
122
- if (!this.#live)
123
- return err(custodyError("key_not_found"));
124
- const connected = await this.#connect(c);
125
- if (!connected.ok)
126
- return connected;
127
- const socket = connected.value;
128
- try {
129
- const reply = await this.#exchange(socket, Buffer.from([11]), c);
130
- if (!reply.ok)
131
- return reply;
132
- const keys = identities(reply.value);
133
- if (!keys.some((k) => k.equals(this.#blob)))
134
- return err(custodyError(initial ? "custodian_rejected" : "key_not_found"));
135
- const signed = await this.#exchange(socket, Buffer.concat([
136
- Buffer.from([13]),
137
- string(this.#blob),
138
- string(data),
139
- uint32(this.#spec.jose === "RS256" ? 2 : 0),
140
- ]), c);
141
- if (!signed.ok)
142
- return signed;
143
- const raw = signature(signed.value, this.#spec, this.#public);
144
- const checked = await verifyPublicKey(this.#public, selection(this.#spec), raw, data);
145
- if (!checked.ok) {
146
- raw.fill(0);
147
- return err(custodyError("custodian_rejected"));
148
- }
149
- return ok(raw);
150
- }
151
- catch (e) {
152
- return failure(e);
153
- }
154
- finally {
155
- socket.destroy();
156
- }
157
- });
158
- }
159
- async sign(_algorithm, data, c) {
160
- if (!(data instanceof Uint8Array))
161
- return err(custodyError("invalid_data"));
162
- return this.#sign(new Uint8Array(data), c);
163
- }
164
- async verify(algorithm, sig, data, _c) {
165
- return verifyPublicKey(this.#public, algorithm, sig, data);
166
- }
167
- async publicKey() {
168
- return ok(this.#public);
169
- }
170
- async identity() {
171
- return KeyIdentity.fromPublic(this.#public);
172
- }
173
- async unwrap() {
174
- return err(custodyError("unsupported_operation"));
175
- }
176
- async release() {
177
- this.#live = false;
178
- for (const s of this.#sockets)
179
- s.destroy();
180
- return ok(true);
181
- }
182
- }
8
+ import { AgentPort, failure } from "./port.js";
183
9
  /** Caller-selected local socket and trusted public components; no environment discovery. */
184
10
  export async function createSshAgent(algorithm, socketPath, publicKey, options = {}) {
185
11
  try {
@@ -225,4 +51,8 @@ export async function createSshAgent(algorithm, socketPath, publicKey, options =
225
51
  return failure(e);
226
52
  }
227
53
  }
228
- export const SSHAgent = Object.freeze({ create: createSshAgent });
54
+ /** Caller-selected OpenSSH agent custody construction. */
55
+ export const SSHAgent = Object.freeze({
56
+ /** Bind an algorithm and public key to a caller-selected OpenSSH agent socket. */
57
+ create: createSshAgent
58
+ });
@@ -0,0 +1,19 @@
1
+ import { type Context } from "../../context.ts";
2
+ import { type Custodian } from "../../key_handle.ts";
3
+ import { KeyIdentity } from "../../key_identity.ts";
4
+ import { type PublicKey } from "../../public_key.ts";
5
+ import { type Spec } from "../../crypto/algorithm.ts";
6
+ import { type Result } from "../../result.ts";
7
+ export declare function failure<T>(e: unknown): Result<T>;
8
+ /** @internal Direct custodian implementation; use the public factory for handles. */
9
+ export declare class AgentPort implements Custodian {
10
+ #private;
11
+ constructor(path: string, publicKey: PublicKey, spec: Spec);
12
+ possession(c: Context, initial?: boolean): Promise<Result<Uint8Array<ArrayBuffer>>>;
13
+ sign(_algorithm: unknown, data: unknown, c: Context): Promise<Result<Uint8Array<ArrayBuffer>>>;
14
+ verify(algorithm: unknown, sig: unknown, data: unknown, _c: Context): Promise<Result<true>>;
15
+ publicKey(): Promise<Result<PublicKey>>;
16
+ identity(): Promise<Result<KeyIdentity>>;
17
+ unwrap(): Promise<ReturnType<Custodian["unwrap"]> extends Promise<infer T> ? T : never>;
18
+ release(): Promise<Result<true>>;
19
+ }
@@ -0,0 +1,183 @@
1
+ import { CRYPTO_MAX_BYTES_CEILING } from "../../constants.js";
2
+ import { boundedBinary } from "../../crypto/primitive.js";
3
+ import { connect } from "node:net";
4
+ import { run } from "../../context.js";
5
+ import { custodyError, isRequestSealError } from "../../errors/types.js";
6
+ import { KeyIdentity } from "../../key_identity.js";
7
+ import { verifyPublicKey, } from "../../public_key.js";
8
+ import { selection, } from "../../crypto/algorithm.js";
9
+ import { ok, err } from "../../result.js";
10
+ import { MAX_FRAME, uint32, string, identities, signature, keyBlob, } from "./wire.js";
11
+ export function failure(e) {
12
+ return err(isRequestSealError(e) && e.kind === "custody"
13
+ ? e
14
+ : custodyError("custodian_protocol"));
15
+ }
16
+ /** @internal Direct custodian implementation; use the public factory for handles. */
17
+ export class AgentPort {
18
+ #path;
19
+ #public;
20
+ #spec;
21
+ #blob;
22
+ #live = true;
23
+ #sockets = new Set();
24
+ constructor(path, publicKey, spec) {
25
+ this.#path = path;
26
+ this.#public = publicKey;
27
+ this.#spec = spec;
28
+ this.#blob = keyBlob(publicKey);
29
+ Object.freeze(this);
30
+ }
31
+ async #connect(c) {
32
+ return new Promise((resolve) => {
33
+ const s = connect(this.#path);
34
+ this.#sockets.add(s);
35
+ let settled = false;
36
+ const done = (r) => {
37
+ if (settled)
38
+ return;
39
+ settled = true;
40
+ s.removeListener("error", error);
41
+ c.signal.removeEventListener("abort", abort);
42
+ resolve(r);
43
+ };
44
+ const abort = () => {
45
+ s.destroy();
46
+ done(err(custodyError("deadline_exceeded")));
47
+ };
48
+ const error = (e) => {
49
+ s.destroy();
50
+ done(err(custodyError(["ENOENT", "ECONNREFUSED", "ECONNRESET"].includes(e.code ?? "")
51
+ ? "custodian_unavailable"
52
+ : "custodian_failure")));
53
+ };
54
+ // A write error can arrive after an exchange has settled; keep it contained until close.
55
+ s.on("error", () => s.destroy());
56
+ s.once("close", () => this.#sockets.delete(s));
57
+ s.once("error", error);
58
+ s.once("connect", () => done(ok(s)));
59
+ c.signal.addEventListener("abort", abort, { once: true });
60
+ if (c.signal.aborted)
61
+ abort();
62
+ });
63
+ }
64
+ async #exchange(socket, request, c) {
65
+ return new Promise((resolve) => {
66
+ let buffer = Buffer.alloc(0), settled = false;
67
+ const done = (r) => {
68
+ if (settled)
69
+ return;
70
+ settled = true;
71
+ socket.removeListener("data", data);
72
+ socket.removeListener("error", error);
73
+ socket.removeListener("end", error);
74
+ socket.removeListener("close", error);
75
+ c.signal.removeEventListener("abort", abort);
76
+ resolve(r);
77
+ };
78
+ const error = () => done(err(custodyError("custodian_protocol")));
79
+ const abort = () => {
80
+ socket.destroy();
81
+ done(err(custodyError("deadline_exceeded")));
82
+ };
83
+ const data = (chunk) => {
84
+ if (buffer.length + chunk.length > MAX_FRAME + 4) {
85
+ socket.destroy();
86
+ error();
87
+ return;
88
+ }
89
+ buffer = Buffer.concat([buffer, chunk]);
90
+ if (buffer.length < 4)
91
+ return;
92
+ const length = buffer.readUInt32BE(0);
93
+ if (length < 1 || length > MAX_FRAME || buffer.length > length + 4) {
94
+ socket.destroy();
95
+ error();
96
+ return;
97
+ }
98
+ if (buffer.length === length + 4)
99
+ done(ok(new Uint8Array(buffer.subarray(4))));
100
+ };
101
+ socket.on("data", data);
102
+ socket.once("error", error);
103
+ socket.once("end", error);
104
+ socket.once("close", error);
105
+ c.signal.addEventListener("abort", abort, { once: true });
106
+ if (c.signal.aborted) {
107
+ abort();
108
+ return;
109
+ }
110
+ socket.write(Buffer.concat([uint32(request.length), request]), (e) => {
111
+ if (e)
112
+ error();
113
+ });
114
+ });
115
+ }
116
+ async possession(c, initial = false) {
117
+ return this.#sign(new Uint8Array(), c, initial);
118
+ }
119
+ async #sign(data, c, initial = false) {
120
+ return run(c, async () => {
121
+ if (!this.#live)
122
+ return err(custodyError("key_not_found"));
123
+ const connected = await this.#connect(c);
124
+ if (!connected.ok)
125
+ return connected;
126
+ const socket = connected.value;
127
+ try {
128
+ const reply = await this.#exchange(socket, Buffer.from([11]), c);
129
+ if (!reply.ok)
130
+ return reply;
131
+ const keys = identities(reply.value);
132
+ if (!keys.some((k) => k.equals(this.#blob)))
133
+ return err(custodyError(initial ? "custodian_rejected" : "key_not_found"));
134
+ const signed = await this.#exchange(socket, Buffer.concat([
135
+ Buffer.from([13]),
136
+ string(this.#blob),
137
+ string(data),
138
+ uint32(this.#spec.jose === "RS256" ? 2 : 0),
139
+ ]), c);
140
+ if (!signed.ok)
141
+ return signed;
142
+ const raw = signature(signed.value, this.#spec, this.#public);
143
+ const checked = await verifyPublicKey(this.#public, selection(this.#spec), raw, data, CRYPTO_MAX_BYTES_CEILING);
144
+ if (!checked.ok) {
145
+ raw.fill(0);
146
+ return err(custodyError("custodian_rejected"));
147
+ }
148
+ return ok(raw);
149
+ }
150
+ catch (e) {
151
+ return failure(e);
152
+ }
153
+ finally {
154
+ socket.destroy();
155
+ }
156
+ });
157
+ }
158
+ async sign(_algorithm, data, c) {
159
+ if (!boundedBinary(data, CRYPTO_MAX_BYTES_CEILING))
160
+ return err(custodyError("invalid_data"));
161
+ return this.#sign(new Uint8Array(data), c);
162
+ }
163
+ async verify(algorithm, sig, data, _c) {
164
+ if (!boundedBinary(data, CRYPTO_MAX_BYTES_CEILING))
165
+ return err(custodyError("invalid_data"));
166
+ return verifyPublicKey(this.#public, algorithm, sig, data, CRYPTO_MAX_BYTES_CEILING);
167
+ }
168
+ async publicKey() {
169
+ return ok(this.#public);
170
+ }
171
+ async identity() {
172
+ return KeyIdentity.fromPublic(this.#public);
173
+ }
174
+ async unwrap() {
175
+ return err(custodyError("unsupported_operation"));
176
+ }
177
+ async release() {
178
+ this.#live = false;
179
+ for (const s of this.#sockets)
180
+ s.destroy();
181
+ return ok(true);
182
+ }
183
+ }
package/dist/digest.d.ts CHANGED
@@ -1,42 +1,64 @@
1
1
  import { type Dictionary } from "./structured-fields/index.ts";
2
2
  import { type Message } from "./message/index.ts";
3
3
  import { type Result } from "./result.ts";
4
+ /** Supported content-digest algorithm identifiers. */
4
5
  export type DigestAlgorithm = "sha-256" | "sha-512";
6
+ /** Content or representation digest field selection. */
5
7
  export type DigestKind = "content" | "representation";
8
+ /** A caller-owned incremental hashing session. */
6
9
  export type HashSession = {
10
+ /** Hash an owned chunk while enforcing state and total byte limits. */
7
11
  update: (chunk: Uint8Array) => void | Promise<void>;
12
+ /** Finalize the selected digest algorithms into a canonical digest field. */
8
13
  finish: () => Uint8Array | Promise<Uint8Array>;
9
14
  };
10
15
  /** create, update, and finish must be own properties; bind class prototype methods explicitly. */
11
16
  export type IncrementalHasher = {
17
+ /** Create a bounded digest state after validating the incremental hasher. */
12
18
  create: (algorithm: DigestAlgorithm) => HashSession | Promise<HashSession>;
13
19
  };
20
+ /** Verified digest kind, algorithms, section, and processed byte count. */
14
21
  export type DigestFacts = Readonly<{
15
22
  kind: "content-digest" | "repr-digest";
16
23
  checked: readonly string[];
17
24
  bytes: number;
18
25
  unsupported: readonly string[];
19
26
  }>;
27
+ /** Parse a Content-Digest dictionary and validate its members. */
20
28
  export declare function parseDigest(bytes: unknown): Result<Dictionary>;
29
+ /** Serialize and validate a digest dictionary. */
21
30
  export declare function serializeDigest(value: unknown): Result<Uint8Array>;
31
+ /** Compute a canonical digest field for the selected algorithms. */
22
32
  export declare function digest(content: unknown, names: unknown, options?: unknown): Promise<Result<Uint8Array>>;
33
+ /** Check the private brand of an incremental digest state. */
23
34
  export declare function isDigestState(value: unknown): value is DigestState;
35
+ /** A branded, bounded incremental digest computation. */
24
36
  export declare class DigestState {
25
37
  #private;
26
38
  readonly kind: DigestKind;
27
39
  readonly maxBytes: number;
28
40
  constructor(t: object, kind: DigestKind, max: number, sessions: readonly (readonly [DigestAlgorithm, HashSession])[]);
29
41
  get bytes(): number;
42
+ /** Create a bounded digest state after validating the incremental hasher. */
30
43
  static create(kind: unknown, names: unknown, hasher: unknown, options?: unknown): Promise<Result<DigestState>>;
44
+ /** Hash an owned chunk while enforcing state and total byte limits. */
31
45
  update(chunk: unknown): Promise<Result<DigestState>>;
46
+ /** Finalize the selected digest algorithms into a canonical digest field. */
32
47
  finish(): Promise<Result<Uint8Array>>;
48
+ /** Return a bounded public inspection view without retained cryptographic bytes. */
33
49
  toJSON(): unknown;
34
50
  }
35
51
  export declare function digestField(message: Message, kind: DigestKind, section: "headers" | "trailers"): Result<Dictionary>;
52
+ /** Check required digest fields against retained bytes or an incremental state. */
36
53
  export declare function checkDigest(message: unknown, kind: unknown, options?: unknown): Promise<Result<DigestFacts>>;
54
+ /** Content-digest computation, parsing, serialization, and checking. */
37
55
  export declare const Digest: Readonly<{
56
+ /** Compute the selected content digests over owned body bytes. */
38
57
  content: typeof digest;
58
+ /** Parse and validate the supported wire representation. */
39
59
  parse: typeof parseDigest;
60
+ /** Serialize validated values to their canonical wire representation. */
40
61
  serialize: typeof serializeDigest;
62
+ /** Check required content digests against retained body bytes. */
41
63
  check: typeof checkDigest;
42
64
  }>;
package/dist/digest.js CHANGED
@@ -89,10 +89,12 @@ function validate(value) {
89
89
  return err(error(e, "invalid_field"));
90
90
  }
91
91
  }
92
+ /** Parse a Content-Digest dictionary and validate its members. */
92
93
  export function parseDigest(bytes) {
93
94
  const r = sfParse("dictionary", bytes, { schema });
94
95
  return r.ok ? validate(r.value) : r;
95
96
  }
97
+ /** Serialize and validate a digest dictionary. */
96
98
  export function serializeDigest(value) {
97
99
  const r = sfSerialize(value, { schema });
98
100
  if (!r.ok)
@@ -100,6 +102,7 @@ export function serializeDigest(value) {
100
102
  const parsed = parseDigest(r.value);
101
103
  return parsed.ok ? r : parsed;
102
104
  }
105
+ /** Compute a canonical digest field for the selected algorithms. */
103
106
  export async function digest(content, names, options = {}) {
104
107
  try {
105
108
  ensure(content instanceof Uint8Array, "invalid_body");
@@ -132,11 +135,13 @@ const knownAnswers = Object.freeze({
132
135
  ]),
133
136
  });
134
137
  const states = new WeakSet();
138
+ /** Check the private brand of an incremental digest state. */
135
139
  export function isDigestState(value) {
136
140
  return typeof value === "object" && value !== null && states.has(value);
137
141
  }
138
142
  const token = Object.freeze({});
139
143
  const busySessions = new WeakSet();
144
+ /** A branded, bounded incremental digest computation. */
140
145
  export class DigestState {
141
146
  kind;
142
147
  maxBytes;
@@ -157,6 +162,7 @@ export class DigestState {
157
162
  get bytes() {
158
163
  return this.#bytes;
159
164
  }
165
+ /** Create a bounded digest state after validating the incremental hasher. */
160
166
  static async create(kind, names, hasher, options = {}) {
161
167
  try {
162
168
  ensure(kind === "content" || kind === "representation", "invalid_kind");
@@ -226,6 +232,7 @@ export class DigestState {
226
232
  return err(error(e, "invalid_state"));
227
233
  }
228
234
  }
235
+ /** Hash an owned chunk while enforcing state and total byte limits. */
229
236
  async update(chunk) {
230
237
  try {
231
238
  ensure(isDigestState(this) && !this.#invalid && !this.#finished && !this.#busy, "invalid_state");
@@ -251,6 +258,7 @@ export class DigestState {
251
258
  return err(error(e, "invalid_state"));
252
259
  }
253
260
  }
261
+ /** Finalize the selected digest algorithms into a canonical digest field. */
254
262
  async finish() {
255
263
  try {
256
264
  ensure(isDigestState(this) && !this.#invalid && !this.#busy, "invalid_state");
@@ -283,6 +291,7 @@ export class DigestState {
283
291
  return err(error(e, "invalid_state"));
284
292
  }
285
293
  }
294
+ /** Return a bounded public inspection view without retained cryptographic bytes. */
286
295
  toJSON() {
287
296
  return { kind: this.kind, bytes: this.#bytes, maxBytes: this.maxBytes };
288
297
  }
@@ -297,6 +306,7 @@ export function digestField(message, kind, section) {
297
306
  : r;
298
307
  return validate(r.value);
299
308
  }
309
+ /** Check required digest fields against retained bytes or an incremental state. */
300
310
  export async function checkDigest(message, kind, options = {}) {
301
311
  try {
302
312
  ensure(isMessage(message), "invalid_message");
@@ -362,9 +372,14 @@ export async function checkDigest(message, kind, options = {}) {
362
372
  return err(error(e, "invalid_state"));
363
373
  }
364
374
  }
375
+ /** Content-digest computation, parsing, serialization, and checking. */
365
376
  export const Digest = Object.freeze({
377
+ /** Compute the selected content digests over owned body bytes. */
366
378
  content: digest,
379
+ /** Parse and validate the supported wire representation. */
367
380
  parse: parseDigest,
381
+ /** Serialize validated values to their canonical wire representation. */
368
382
  serialize: serializeDigest,
383
+ /** Check required content digests against retained body bytes. */
369
384
  check: checkDigest,
370
385
  });
@@ -1,15 +1,25 @@
1
1
  import { type Context } from "../context.ts";
2
2
  import { PublicKey } from "../public_key.ts";
3
- import { type HttpAlgorithm, type SigningAlgorithm } from "../crypto/algorithm.ts";
3
+ import { type JwsAlgorithm, type SigningAlgorithm } from "../crypto/algorithm.ts";
4
4
  import { type Result } from "../result.ts";
5
5
  import { type DiscoverySource } from "./source.ts";
6
+ /** Discovery proof status. @inline */
6
7
  export type Proof = "signed" | "unsigned" | "not_applicable";
8
+ /** A conditional revalidation outcome: the stored representation is unchanged (RFC 9111 Section 4.3.4). */
9
+ export type NotModified = Readonly<{
10
+ notModified: true;
11
+ etag: string;
12
+ fetchedAt: number;
13
+ expiresAt: number;
14
+ }>;
15
+ /** A branded resolved public key with origin, proof, and expiration facts. */
7
16
  export type Resolution = Readonly<{
8
17
  key: PublicKey;
9
18
  keyId: string;
10
19
  thumbprint: string;
11
20
  algorithm: SigningAlgorithm;
12
- assertedAlgorithm: HttpAlgorithm | null;
21
+ /** The source's alg assertion normalized once at import to its JOSE name. */
22
+ assertedAlgorithm: JwsAlgorithm | null;
13
23
  origin: string;
14
24
  sourceType: DiscoverySource["type"];
15
25
  location: string;
@@ -18,9 +28,12 @@ export type Resolution = Readonly<{
18
28
  proof: Proof;
19
29
  revision: string | null;
20
30
  }>;
31
+ /** Check the private brand of a discovery resolution. */
21
32
  export declare function isResolution(v: unknown): v is Resolution;
33
+ /** Check the private brand of a discovery key set. */
22
34
  export declare function isKeySet(v: unknown): v is KeySet;
23
35
  export declare function resolution(data: Resolution): Resolution;
36
+ /** A branded immutable discovery key set with bounded key lookup. */
24
37
  export type KeySet = Readonly<{
25
38
  source: DiscoverySource;
26
39
  origin: string;
@@ -29,10 +42,14 @@ export type KeySet = Readonly<{
29
42
  expiresAt: number;
30
43
  revision: string;
31
44
  proof: Proof;
45
+ /** The source's ETag validator, if it sent one; the cache revalidates with it. */
46
+ etag: string | null;
47
+ /** Resolve an eligible key by identifier, allowed algorithms, and current time. */
32
48
  lookup: (keyId: unknown, algorithms: unknown, now?: number) => Promise<Result<Resolution>>;
33
49
  }>;
34
50
  export declare function algorithmList(input: unknown): readonly SigningAlgorithm[];
35
51
  export declare function keySet(data: Omit<KeySet, "lookup">): KeySet;
52
+ /** Resolve an eligible key by identifier, allowed algorithms, and current time. */
36
53
  export declare function lookup(set: unknown, kid: unknown, allowed: unknown, now?: number, transient?: boolean): Promise<Result<Resolution>>;
37
54
  export declare function bodyDocument(bytes: unknown, source: DiscoverySource): Record<string, unknown>;
38
55
  export declare function bodyKeys(doc: Record<string, unknown>, source: DiscoverySource, now: number, location?: string, context?: Context): Promise<Readonly<Record<string, Resolution>>>;