@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.
- package/CHANGELOG.md +49 -1
- package/README.md +342 -114
- package/dist/accept-signature.d.ts +11 -0
- package/dist/accept-signature.js +7 -0
- package/dist/adapters/fetch/index.d.ts +10 -0
- package/dist/adapters/fetch/index.js +21 -4
- package/dist/adapters/fetch/runtime.d.ts +1 -0
- package/dist/adapters/fetch/runtime.js +1 -0
- package/dist/adapters/node-http/index.d.ts +23 -0
- package/dist/adapters/node-http/index.js +28 -8
- package/dist/brand.d.ts +5 -0
- package/dist/brand.js +12 -0
- package/dist/bytes.d.ts +4 -0
- package/dist/bytes.js +5 -0
- package/dist/constants.d.ts +64 -4
- package/dist/constants.js +62 -2
- package/dist/context.d.ts +4 -0
- package/dist/context.js +3 -0
- package/dist/crypto/algorithm.d.ts +9 -0
- package/dist/crypto/algorithm.js +12 -0
- package/dist/crypto/index.d.ts +12 -2
- package/dist/crypto/index.js +34 -11
- package/dist/crypto/options.d.ts +3 -0
- package/dist/crypto/options.js +15 -0
- package/dist/crypto/primitive.d.ts +7 -3
- package/dist/crypto/primitive.js +36 -10
- package/dist/custody/content_key.d.ts +4 -0
- package/dist/custody/content_key.js +4 -2
- package/dist/custody/index.d.ts +2 -0
- package/dist/custody/index.js +5 -1
- package/dist/custody/local.d.ts +17 -1
- package/dist/custody/local.js +10 -5
- package/dist/custody/ssh-agent/index.d.ts +2 -0
- package/dist/custody/ssh-agent/index.js +11 -181
- package/dist/custody/ssh-agent/port.d.ts +19 -0
- package/dist/custody/ssh-agent/port.js +183 -0
- package/dist/digest.d.ts +22 -0
- package/dist/digest.js +15 -0
- package/dist/discovery/body.d.ts +19 -2
- package/dist/discovery/body.js +36 -11
- package/dist/discovery/cache.d.ts +21 -2
- package/dist/discovery/cache.js +105 -39
- package/dist/discovery/headers.d.ts +3 -0
- package/dist/discovery/headers.js +27 -1
- package/dist/discovery/index.d.ts +13 -1
- package/dist/discovery/index.js +31 -7
- package/dist/discovery/node-connection.d.ts +1 -1
- package/dist/discovery/node-connection.js +2 -2
- package/dist/discovery/node-trust.d.ts +2 -0
- package/dist/discovery/node-trust.js +9 -0
- package/dist/discovery/node.d.ts +9 -1
- package/dist/discovery/node.js +150 -26
- package/dist/discovery/proof.js +2 -2
- package/dist/discovery/snapshot.js +3 -0
- package/dist/discovery/source.d.ts +7 -0
- package/dist/discovery/source.js +5 -0
- package/dist/discovery/support.js +2 -3
- package/dist/errors/parity.d.ts +44 -3
- package/dist/errors/parity.js +45 -4
- package/dist/errors/types.d.ts +17 -3
- package/dist/errors/types.js +16 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/jose/header.d.ts +1 -0
- package/dist/jose/jwe.d.ts +9 -0
- package/dist/jose/jwe.js +17 -3
- package/dist/jose/jws.d.ts +11 -0
- package/dist/jose/jws.js +14 -2
- package/dist/jose/key-management.d.ts +11 -0
- package/dist/jose/key-management.js +13 -2
- package/dist/jose/nested.d.ts +6 -0
- package/dist/jose/nested.js +32 -2
- package/dist/json.d.ts +7 -0
- package/dist/key_handle.d.ts +12 -3
- package/dist/key_handle.js +32 -25
- package/dist/key_identity.d.ts +7 -0
- package/dist/key_identity.js +10 -2
- package/dist/message/index.d.ts +24 -0
- package/dist/message/index.js +22 -1
- package/dist/numbers.d.ts +6 -0
- package/dist/numbers.js +5 -0
- package/dist/policy.d.ts +27 -0
- package/dist/policy.js +42 -21
- package/dist/profile.d.ts +36 -0
- package/dist/profile.js +215 -0
- package/dist/public_key.d.ts +11 -2
- package/dist/public_key.js +31 -8
- package/dist/quorum/evaluation.d.ts +1 -1
- package/dist/quorum/evaluation.js +18 -4
- package/dist/quorum/verification.d.ts +2 -0
- package/dist/quorum/verification.js +1 -0
- package/dist/quorum.d.ts +9 -0
- package/dist/quorum.js +7 -1
- package/dist/replay/claim.d.ts +5 -0
- package/dist/replay/claim.js +9 -1
- package/dist/replay/index.d.ts +6 -0
- package/dist/replay/index.js +13 -1
- package/dist/replay/postgres.d.ts +3 -0
- package/dist/replay/postgres.js +7 -0
- package/dist/replay/receipt.d.ts +1 -0
- package/dist/replay/receipt.js +1 -0
- package/dist/replay/store.d.ts +1 -0
- package/dist/replay/store.js +1 -0
- package/dist/replay-port.d.ts +6 -1
- package/dist/result.d.ts +3 -0
- package/dist/result.js +2 -0
- package/dist/sign.d.ts +26 -0
- package/dist/sign.js +25 -3
- package/dist/signature-base/index.d.ts +2 -0
- package/dist/signature-base/index.js +5 -1
- package/dist/signature-fields.d.ts +5 -1
- package/dist/signature-fields.js +9 -1
- package/dist/structured-fields/index.d.ts +21 -0
- package/dist/structured-fields/index.js +14 -1
- package/dist/verification.d.ts +5 -1
- package/dist/verification.js +9 -6
- package/dist/verify-quorum.d.ts +1 -0
- package/dist/verify-quorum.js +30 -8
- package/dist/verify.d.ts +23 -0
- package/dist/verify.js +46 -14
- package/dist/web-bot-auth/index.d.ts +3 -0
- package/dist/web-bot-auth/index.js +3 -0
- package/dist/web-bot-auth/policy.d.ts +8 -0
- package/dist/web-bot-auth/policy.js +8 -4
- package/dist/web-bot-auth/verification.d.ts +8 -0
- package/dist/web-bot-auth/verification.js +4 -2
- package/dist/web-bot-auth/verify.js +2 -2
- 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
|
|
4
|
-
import { custodyError
|
|
5
|
-
import { createKeyHandle
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
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
|
});
|
package/dist/discovery/body.d.ts
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import { type Context } from "../context.ts";
|
|
2
2
|
import { PublicKey } from "../public_key.ts";
|
|
3
|
-
import { type
|
|
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
|
-
|
|
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>>>;
|