@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
package/dist/verify-quorum.js
CHANGED
|
@@ -3,12 +3,13 @@ import { isMessage, Message } from "./message/index.js";
|
|
|
3
3
|
import { isQuorum } from "./quorum.js";
|
|
4
4
|
import { isContext, withDeadline, remainingMs } from "./context.js";
|
|
5
5
|
import { isAcceptSignature, applicable, } from "./accept-signature.js";
|
|
6
|
-
import { dictionaries, parameters, identities, ensure, fail,
|
|
6
|
+
import { dictionaries, parameters, identities, ensure, fail, envelope, } from "./signature-fields.js";
|
|
7
7
|
import { verifyCandidate } from "./verify.js";
|
|
8
8
|
import { evaluateQuorum } from "./quorum/evaluation.js";
|
|
9
9
|
import { completeQuorum, } from "./quorum/verification.js";
|
|
10
10
|
import { custodyError, isRequestSealError } from "./errors/types.js";
|
|
11
11
|
import { ok, err } from "./result.js";
|
|
12
|
+
/** Verify bounded HTTP signatures and evaluate the configured quorum policy. */
|
|
12
13
|
export async function verifyQuorum(message, quorum, options) {
|
|
13
14
|
try {
|
|
14
15
|
const a = snapshotOptions(options, ["acceptSignature", "context"]);
|
|
@@ -55,10 +56,30 @@ export async function verifyQuorum(message, quorum, options) {
|
|
|
55
56
|
let qualified = false, last;
|
|
56
57
|
const baseCache = new Map();
|
|
57
58
|
for (const slot of slots) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
let r;
|
|
60
|
+
try {
|
|
61
|
+
r = await verifyCandidate(m, slot.policy, {
|
|
62
|
+
label: l,
|
|
63
|
+
context: c.value,
|
|
64
|
+
}, { input, signature: signatures.get(l), baseCache });
|
|
65
|
+
}
|
|
66
|
+
catch (fault) {
|
|
67
|
+
// Per-label rescue isolates a defective label as its own
|
|
68
|
+
// nonqualifying internal fact, never a whole-call failure.
|
|
69
|
+
if (isRequestSealError(fault) && fault.kind === "custody")
|
|
70
|
+
return err(fault);
|
|
71
|
+
const faulted = envelope(fault);
|
|
72
|
+
if (faulted.kind !== "core")
|
|
73
|
+
throw fault;
|
|
74
|
+
last = {
|
|
75
|
+
label: l,
|
|
76
|
+
slot: slot.id,
|
|
77
|
+
reason: faulted.reason,
|
|
78
|
+
layer: faulted.layer,
|
|
79
|
+
};
|
|
80
|
+
facts.push(last);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
62
83
|
if (r.ok) {
|
|
63
84
|
const record = Object.freeze({
|
|
64
85
|
slot,
|
|
@@ -107,8 +128,9 @@ export async function verifyQuorum(message, quorum, options) {
|
|
|
107
128
|
const original = candidates.find((c) => c.slot === r.slot && c.label === r.label);
|
|
108
129
|
results[r.label] = verified.get(original);
|
|
109
130
|
}
|
|
110
|
-
|
|
111
|
-
|
|
131
|
+
// The wall-clock bound lives in the assignment search: an accepted
|
|
132
|
+
// assignment, including one accepted at deadline exhaustion, completes.
|
|
133
|
+
// The remaining work here is synchronous and bounded.
|
|
112
134
|
return completeQuorum({
|
|
113
135
|
profile: { name: "rfc9421" },
|
|
114
136
|
mode: quorum.mode,
|
|
@@ -139,6 +161,6 @@ export async function verifyQuorum(message, quorum, options) {
|
|
|
139
161
|
catch (e) {
|
|
140
162
|
if (isRequestSealError(e) && e.kind === "custody")
|
|
141
163
|
return err(e);
|
|
142
|
-
return err(
|
|
164
|
+
return err(envelope(e));
|
|
143
165
|
}
|
|
144
166
|
}
|
package/dist/verify.d.ts
CHANGED
|
@@ -4,7 +4,10 @@ import { KeyIdentity } from "./key_identity.ts";
|
|
|
4
4
|
import { build } from "./signature-base/index.ts";
|
|
5
5
|
import { type Verification } from "./verification.ts";
|
|
6
6
|
import type { InnerList, Item } from "./structured-fields/index.ts";
|
|
7
|
+
import type { ReplayFacts } from "./replay-port.ts";
|
|
7
8
|
import { type Result } from "./result.ts";
|
|
9
|
+
/** Internal coverage/freshness pipeline shared by selected verification and preflight. */
|
|
10
|
+
export declare function authenticationPreflight(input: InnerList, components: InnerList, extra: Policy["extraComponents"], freshnessPolicy: Policy["freshness"] | (() => Policy["freshness"]), context: Context): Promise<Verification["freshness"]>;
|
|
8
11
|
/** Internal shared commit boundary; profile facts are supplied only after authentication. */
|
|
9
12
|
export declare function commitReplay<F>(facts: F, retainUntil: number, policy: Omit<import('./policy.ts').ReplayPolicy, 'commitment'> & {
|
|
10
13
|
commitment: (facts: F, context: Context) => Result<Uint8Array> | Promise<Result<Uint8Array>>;
|
|
@@ -16,6 +19,26 @@ type ParsedCandidate = Readonly<{
|
|
|
16
19
|
signature: Item;
|
|
17
20
|
baseCache: BaseCache;
|
|
18
21
|
}>;
|
|
22
|
+
export declare function verifyLabel(message: unknown, policy: unknown, options: unknown, observeIdentity?: (identity: KeyIdentity) => void, parsed?: ParsedCandidate, profile?: ReplayFacts["profile"]): Promise<Result<Verification>>;
|
|
23
|
+
/**
|
|
24
|
+
* Verify one HTTP signature under an explicit branded policy.
|
|
25
|
+
*
|
|
26
|
+
* Checks exactly the named label; it never tries other labels. Success means the
|
|
27
|
+
* cryptography, coverage, freshness, content, and replay layers all passed. It does not
|
|
28
|
+
* authorize: the Verification records `authorization: "not_evaluated"`.
|
|
29
|
+
*
|
|
30
|
+
* @param message - A branded Message carrying the signature.
|
|
31
|
+
* @param policy - A branded Policy from `Policy.create`.
|
|
32
|
+
* @param options - `{label, context?, representation?, digestState?}`. `representation` is a
|
|
33
|
+
* complete retained Body and needs a `"representation"` content policy. `digestState` is a
|
|
34
|
+
* DigestState of the policy's content kind that you fed with a streamed body; verify finishes
|
|
35
|
+
* it. The two are mutually exclusive.
|
|
36
|
+
* @returns `ok(verification)`, or a bounded error such as `core` `crypto.invalid_signature`,
|
|
37
|
+
* `freshness.expired`, `content.digest_mismatch`, `key.unknown_key`, or `replay.replayed`.
|
|
38
|
+
* @see [Signing and verifying guide](https://github.com/baselabs/request-seal-typescript/blob/main/docs/guides/signing-and-verifying.md),
|
|
39
|
+
* [reference](https://github.com/baselabs/request-seal-typescript/blob/main/docs/reference/sign-and-verify.md), and the
|
|
40
|
+
* [error reasons](https://github.com/baselabs/request-seal-typescript/blob/main/docs/reference/errors.md).
|
|
41
|
+
*/
|
|
19
42
|
export declare function verify(message: unknown, policy: unknown, options: unknown): Promise<Result<Verification>>;
|
|
20
43
|
/** Internal composition of the single-label pipeline; identities stay private. */
|
|
21
44
|
export declare function verifyCandidate(message: unknown, policy: Policy, options: unknown, parsed?: ParsedCandidate): Promise<Result<Readonly<{
|
package/dist/verify.js
CHANGED
|
@@ -10,7 +10,7 @@ import { isPublicKey } from "./public_key.js";
|
|
|
10
10
|
import { isKeyIdentity, KeyIdentity } from "./key_identity.js";
|
|
11
11
|
import { build } from "./signature-base/index.js";
|
|
12
12
|
import { digestField, checkDigest, isDigestState } from "./digest.js";
|
|
13
|
-
import { label, dictionaries, validInner, parameters, identities, ensure, fail, bounded, } from "./signature-fields.js";
|
|
13
|
+
import { label, dictionaries, validInner, parameters, identities, ensure, fail, bounded, envelope, } from "./signature-fields.js";
|
|
14
14
|
import { completed, } from "./verification.js";
|
|
15
15
|
import { createClaim, retainUntil as retention } from "./replay/claim.js";
|
|
16
16
|
import { createReceipt } from "./replay/receipt.js";
|
|
@@ -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
|
|
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
|
|
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,19 +368,47 @@ async function verifyLabel(message, policy, options, observeIdentity, parsed) {
|
|
|
364
368
|
keyid: (params.keyid ?? null),
|
|
365
369
|
crypto: "valid",
|
|
366
370
|
},
|
|
367
|
-
profile
|
|
371
|
+
profile, content: contentResult.value, freshness: fresh,
|
|
368
372
|
replay: replayFacts, principal: "unattributed", authorization: "not_evaluated",
|
|
369
373
|
});
|
|
370
|
-
if (result.ok && identity)
|
|
371
|
-
|
|
374
|
+
if (result.ok && identity) {
|
|
375
|
+
// An extension pipeline step is a caller callback: its fault keeps the
|
|
376
|
+
// profile's deliberate invalid_policy classification, like a failing
|
|
377
|
+
// signer keeps signer_failed.
|
|
378
|
+
try {
|
|
379
|
+
observeIdentity(identity);
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
fail("invalid_policy", "input");
|
|
383
|
+
}
|
|
384
|
+
}
|
|
372
385
|
return result;
|
|
373
386
|
}
|
|
374
387
|
catch (e) {
|
|
375
388
|
if (isRequestSealError(e) && e.kind === "custody" && e.reason === "deadline_exceeded")
|
|
376
389
|
return err(e);
|
|
377
|
-
return err(
|
|
390
|
+
return err(envelope(e));
|
|
378
391
|
}
|
|
379
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* Verify one HTTP signature under an explicit branded policy.
|
|
395
|
+
*
|
|
396
|
+
* Checks exactly the named label; it never tries other labels. Success means the
|
|
397
|
+
* cryptography, coverage, freshness, content, and replay layers all passed. It does not
|
|
398
|
+
* authorize: the Verification records `authorization: "not_evaluated"`.
|
|
399
|
+
*
|
|
400
|
+
* @param message - A branded Message carrying the signature.
|
|
401
|
+
* @param policy - A branded Policy from `Policy.create`.
|
|
402
|
+
* @param options - `{label, context?, representation?, digestState?}`. `representation` is a
|
|
403
|
+
* complete retained Body and needs a `"representation"` content policy. `digestState` is a
|
|
404
|
+
* DigestState of the policy's content kind that you fed with a streamed body; verify finishes
|
|
405
|
+
* it. The two are mutually exclusive.
|
|
406
|
+
* @returns `ok(verification)`, or a bounded error such as `core` `crypto.invalid_signature`,
|
|
407
|
+
* `freshness.expired`, `content.digest_mismatch`, `key.unknown_key`, or `replay.replayed`.
|
|
408
|
+
* @see [Signing and verifying guide](https://github.com/baselabs/request-seal-typescript/blob/main/docs/guides/signing-and-verifying.md),
|
|
409
|
+
* [reference](https://github.com/baselabs/request-seal-typescript/blob/main/docs/reference/sign-and-verify.md), and the
|
|
410
|
+
* [error reasons](https://github.com/baselabs/request-seal-typescript/blob/main/docs/reference/errors.md).
|
|
411
|
+
*/
|
|
380
412
|
export function verify(message, policy, options) {
|
|
381
413
|
return verifyLabel(message, policy, options);
|
|
382
414
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
[Symbol.for("Deno.customInspect")]: { value: inspect },
|
|
37
|
+
...inspection(inspect),
|
|
36
38
|
});
|
|
37
39
|
envelopes.add(result);
|
|
38
40
|
return ok(Object.freeze(result));
|
|
@@ -10,7 +10,7 @@ import { isDiscoverySource, sourceOrigin, thumbprintId, } from "../discovery/sou
|
|
|
10
10
|
import { isKeySet, isResolution } from "../discovery/body.js";
|
|
11
11
|
import { isPublicKey } from "../public_key.js";
|
|
12
12
|
import { coreError, isRequestSealError, } from "../errors/types.js";
|
|
13
|
-
import { dictionaries, validInner, parameters, identities, ensure, fail,
|
|
13
|
+
import { dictionaries, validInner, parameters, identities, ensure, fail, envelope, } from "../signature-fields.js";
|
|
14
14
|
import { CLOCK_MAX_SECONDS } from "../constants.js";
|
|
15
15
|
import { retentionEnd } from "../replay/claim.js";
|
|
16
16
|
import { agents, identifier, originAgent, component, required, nested, schemas, } from "./support.js";
|
|
@@ -336,7 +336,7 @@ export async function verifyWebBotAuth(message, policy, options = {}) {
|
|
|
336
336
|
catch (e) {
|
|
337
337
|
if (isRequestSealError(e))
|
|
338
338
|
return err(e);
|
|
339
|
-
return err(
|
|
339
|
+
return err(envelope(e));
|
|
340
340
|
}
|
|
341
341
|
finally {
|
|
342
342
|
if (owned)
|
package/package.json
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@request-seal/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"http-message-signatures",
|
|
9
|
+
"rfc9421",
|
|
10
|
+
"rfc9530",
|
|
11
|
+
"content-digest",
|
|
12
|
+
"web-bot-auth",
|
|
13
|
+
"jose",
|
|
14
|
+
"jws",
|
|
15
|
+
"jwe",
|
|
16
|
+
"webcrypto",
|
|
17
|
+
"replay-protection"
|
|
18
|
+
],
|
|
7
19
|
"main": "./dist/index.js",
|
|
8
20
|
"types": "./dist/index.d.ts",
|
|
9
21
|
"exports": {
|
|
@@ -61,19 +73,30 @@
|
|
|
61
73
|
"url": "https://github.com/baselabs/request-seal-typescript/issues"
|
|
62
74
|
},
|
|
63
75
|
"scripts": {
|
|
64
|
-
"typecheck": "tsc -p tsconfig.json",
|
|
65
|
-
"typecheck:web": "tsc -p tsconfig.web.json",
|
|
76
|
+
"typecheck": "node node_modules/typescript/bin/tsc -p tsconfig.json",
|
|
77
|
+
"typecheck:web": "node node_modules/typescript/bin/tsc -p tsconfig.web.json",
|
|
66
78
|
"test": "node --unhandled-rejections=strict --experimental-strip-types --no-warnings --test-concurrency=4 --test test/*.test.ts",
|
|
67
79
|
"test:red": "node --unhandled-rejections=strict --experimental-strip-types --no-warnings --test test/red/planted.test.ts",
|
|
68
80
|
"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",
|
|
81
|
+
"build": "node --experimental-strip-types --no-warnings scripts/clean-dist.ts && node node_modules/typescript/bin/tsc -p tsconfig.build.json",
|
|
70
82
|
"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
83
|
"pack:check": "node --experimental-strip-types --no-warnings scripts/pack-check.ts",
|
|
72
|
-
"check": "npm run typecheck && npm run typecheck:web && npm
|
|
84
|
+
"check": "npm run typecheck && npm run typecheck:web && npm run build && npm test && npm run self-checks && npm run conformance && npm run pack:check",
|
|
85
|
+
"docs": "node --import ./scripts/typedoc-register.mjs node_modules/typedoc/bin/typedoc --options typedoc.json"
|
|
73
86
|
},
|
|
74
87
|
"devDependencies": {
|
|
88
|
+
"@types/markdown-it": "14.1.2",
|
|
75
89
|
"@types/node": "26.6.4",
|
|
76
90
|
"fast-check": "4.10.2",
|
|
77
|
-
"
|
|
91
|
+
"github-slugger": "2.0.0",
|
|
92
|
+
"markdown-it": "14.3.2",
|
|
93
|
+
"typedoc": "0.28.20",
|
|
94
|
+
"typescript": "7.0.2",
|
|
95
|
+
"typescript-docs": "npm:typescript@6.0.3"
|
|
96
|
+
},
|
|
97
|
+
"overrides": {
|
|
98
|
+
"typedoc": {
|
|
99
|
+
"typescript": "$typescript"
|
|
100
|
+
}
|
|
78
101
|
}
|
|
79
102
|
}
|