@request-seal/core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +341 -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 +16 -0
- package/dist/adapters/node-http/index.js +8 -4
- 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 +61 -1
- package/dist/constants.js +61 -1
- package/dist/context.d.ts +4 -0
- package/dist/context.js +3 -0
- package/dist/crypto/algorithm.d.ts +5 -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 +7 -0
- package/dist/discovery/body.js +3 -0
- package/dist/discovery/cache.d.ts +9 -0
- package/dist/discovery/cache.js +2 -0
- package/dist/discovery/headers.d.ts +1 -0
- package/dist/discovery/index.d.ts +5 -0
- package/dist/discovery/index.js +10 -2
- 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 +4 -0
- package/dist/discovery/node.js +29 -11
- 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 +16 -1
- package/dist/errors/parity.js +14 -2
- package/dist/errors/types.d.ts +13 -1
- package/dist/errors/types.js +2 -0
- 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 +9 -1
- 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 +20 -0
- package/dist/numbers.d.ts +6 -0
- package/dist/numbers.js +5 -0
- package/dist/policy.d.ts +15 -0
- package/dist/policy.js +30 -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/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 +1 -0
- package/dist/replay/postgres.js +1 -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 +5 -0
- package/dist/sign.js +1 -0
- package/dist/signature-base/index.d.ts +2 -0
- package/dist/signature-base/index.js +5 -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 +1 -0
- package/dist/verify.d.ts +5 -0
- package/dist/verify.js +16 -11
- 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/package.json +17 -6
package/dist/message/index.js
CHANGED
|
@@ -7,15 +7,19 @@ const messages = new WeakSet();
|
|
|
7
7
|
const fieldOccurrences = new WeakSet();
|
|
8
8
|
const bodies = new WeakSet();
|
|
9
9
|
const transportFacts = new WeakSet();
|
|
10
|
+
/** Check the private brand of an HTTP message. */
|
|
10
11
|
export function isMessage(value) {
|
|
11
12
|
return typeof value === 'object' && value !== null && messages.has(value);
|
|
12
13
|
}
|
|
14
|
+
/** Check the private brand of a field occurrence. */
|
|
13
15
|
export function isFieldOccurrence(value) {
|
|
14
16
|
return typeof value === 'object' && value !== null && fieldOccurrences.has(value);
|
|
15
17
|
}
|
|
18
|
+
/** Check the private brand of a body snapshot. */
|
|
16
19
|
export function isBody(value) {
|
|
17
20
|
return typeof value === 'object' && value !== null && bodies.has(value);
|
|
18
21
|
}
|
|
22
|
+
/** Check the private brand of captured transport facts. */
|
|
19
23
|
export function isTransportFacts(value) {
|
|
20
24
|
return typeof value === 'object' && value !== null && transportFacts.has(value);
|
|
21
25
|
}
|
|
@@ -265,21 +269,32 @@ function message(input, related = false) {
|
|
|
265
269
|
relatedRequest: request,
|
|
266
270
|
}, messages);
|
|
267
271
|
}
|
|
272
|
+
/** Field occurrence construction and validation. */
|
|
268
273
|
export const FieldOccurrence = Object.freeze({
|
|
274
|
+
/** Validate and brand the supplied HTTP snapshot. */
|
|
269
275
|
create: (a) => protect(() => field(a), 'invalid_field'),
|
|
276
|
+
/** Validate the snapshot using the same boundary as construction. */
|
|
270
277
|
validate: (a) => protect(() => field(a), 'invalid_field'),
|
|
271
278
|
});
|
|
279
|
+
/** Body snapshot construction and validation. */
|
|
272
280
|
export const Body = Object.freeze({
|
|
281
|
+
/** Validate and brand the supplied HTTP snapshot. */
|
|
273
282
|
create: (a) => protect(() => body(a), 'invalid_body'),
|
|
283
|
+
/** Validate the snapshot using the same boundary as construction. */
|
|
274
284
|
validate: (a) => protect(() => body(a), 'invalid_body'),
|
|
275
285
|
});
|
|
286
|
+
/** Transport snapshot construction and validation. */
|
|
276
287
|
export const TransportFacts = Object.freeze({
|
|
288
|
+
/** Validate and brand the supplied HTTP snapshot. */
|
|
277
289
|
create: (a) => protect(() => transport(a), 'invalid_transport'),
|
|
290
|
+
/** Validate the snapshot using the same boundary as construction. */
|
|
278
291
|
validate: (a) => protect(() => transport(a), 'invalid_transport'),
|
|
279
292
|
});
|
|
293
|
+
/** Validate and brand a lossless HTTP message snapshot. */
|
|
280
294
|
export function createMessage(a) {
|
|
281
295
|
return protect(() => message(a), 'invalid_message');
|
|
282
296
|
}
|
|
297
|
+
/** Validate a message snapshot through the same constructor boundary. */
|
|
283
298
|
export const validateMessage = createMessage;
|
|
284
299
|
async function builder(kind, first, target, headers, bytes, options) {
|
|
285
300
|
try {
|
|
@@ -347,9 +362,14 @@ async function builder(kind, first, target, headers, bytes, options) {
|
|
|
347
362
|
: messageError('invalid_message'));
|
|
348
363
|
}
|
|
349
364
|
}
|
|
365
|
+
/** Lossless HTTP message construction and validation. */
|
|
350
366
|
export const Message = Object.freeze({
|
|
367
|
+
/** Validate and brand the supplied HTTP snapshot. */
|
|
351
368
|
create: createMessage,
|
|
369
|
+
/** Validate the snapshot using the same boundary as construction. */
|
|
352
370
|
validate: validateMessage,
|
|
371
|
+
/** Build a branded HTTP request with owned field and body bytes. */
|
|
353
372
|
request: (method, target, fields, bytes, options = {}) => builder('request', method, target, fields, bytes, options),
|
|
373
|
+
/** Build a branded HTTP response with owned field and body bytes. */
|
|
354
374
|
response: (status, fields, bytes, options = {}) => builder('response', status, null, fields, bytes, options),
|
|
355
375
|
});
|
package/dist/numbers.d.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { type Result } from './result.ts';
|
|
2
|
+
/** Check whether a value is a JavaScript safe integer. */
|
|
2
3
|
export declare const safeInteger: (value: unknown) => value is number;
|
|
4
|
+
/** Validate the Structured Fields integer range and reject negative zero. */
|
|
3
5
|
export declare function structuredInteger(value: unknown): Result<number>;
|
|
6
|
+
/** Validate a Structured Fields date using the integer range. */
|
|
4
7
|
export declare const structuredDate: typeof structuredInteger;
|
|
8
|
+
/** A decimal stored as an integer scaled by one thousand. */
|
|
5
9
|
export type Decimal = Readonly<{
|
|
6
10
|
scaled: number;
|
|
7
11
|
scale: 3;
|
|
8
12
|
}>;
|
|
9
13
|
/** Parse decimal source text without rounding or a floating-point conversion. */
|
|
10
14
|
export declare function parseDecimal(value: unknown): Result<Decimal>;
|
|
15
|
+
/** Validate a nonnegative Unix clock value within the supported range. */
|
|
11
16
|
export declare function clockSeconds(value: unknown): Result<number>;
|
|
17
|
+
/** Validate a positive timeout within the custody time limit. */
|
|
12
18
|
export declare function timeoutMs(value: unknown): Result<number>;
|
package/dist/numbers.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { SF_MAX_INTEGER, CLOCK_MAX_SECONDS, TIMEOUT_MAX_MS } from './constants.js';
|
|
2
2
|
import { ok, err } from './result.js';
|
|
3
3
|
import { structuredFieldsError, coreError, custodyError } from './errors/types.js';
|
|
4
|
+
/** Check whether a value is a JavaScript safe integer. */
|
|
4
5
|
export const safeInteger = (value) => typeof value === 'number' && Number.isSafeInteger(value);
|
|
6
|
+
/** Validate the Structured Fields integer range and reject negative zero. */
|
|
5
7
|
export function structuredInteger(value) {
|
|
6
8
|
return safeInteger(value) && !Object.is(value, -0) && Math.abs(value) <= SF_MAX_INTEGER ? ok(value) : err(structuredFieldsError('invalid_value'));
|
|
7
9
|
}
|
|
10
|
+
/** Validate a Structured Fields date using the integer range. */
|
|
8
11
|
export const structuredDate = structuredInteger;
|
|
9
12
|
/** Parse decimal source text without rounding or a floating-point conversion. */
|
|
10
13
|
export function parseDecimal(value) {
|
|
@@ -16,9 +19,11 @@ export function parseDecimal(value) {
|
|
|
16
19
|
const magnitude = Number(match[2]) * 1000 + Number(match[3].padEnd(3, '0'));
|
|
17
20
|
return ok(Object.freeze({ scaled: match[1] === '-' && magnitude !== 0 ? -magnitude : magnitude, scale: 3 }));
|
|
18
21
|
}
|
|
22
|
+
/** Validate a nonnegative Unix clock value within the supported range. */
|
|
19
23
|
export function clockSeconds(value) {
|
|
20
24
|
return safeInteger(value) && value >= 0 && value <= CLOCK_MAX_SECONDS ? ok(value) : err(coreError('invalid_clock', 'freshness'));
|
|
21
25
|
}
|
|
26
|
+
/** Validate a positive timeout within the custody time limit. */
|
|
22
27
|
export function timeoutMs(value) {
|
|
23
28
|
return safeInteger(value) && value >= 1 && value <= TIMEOUT_MAX_MS ? ok(value) : err(custodyError('invalid_options'));
|
|
24
29
|
}
|
package/dist/policy.d.ts
CHANGED
|
@@ -6,36 +6,45 @@ import type { KeyIdentity } from "./key_identity.ts";
|
|
|
6
6
|
import type { KeyHandle } from "./key_handle.ts";
|
|
7
7
|
import type { PublicKey } from "./public_key.ts";
|
|
8
8
|
import type { ReplayStore, ReplayFacts } from "./replay-port.ts";
|
|
9
|
+
/** A caller-owned signature verification callback receiving a bounded context. */
|
|
9
10
|
export type Verifier = (algorithm: SigningAlgorithm, base: Uint8Array, signature: Uint8Array, context: Context) => Result<boolean> | Promise<Result<boolean>>;
|
|
11
|
+
/** A resolved algorithm and key or verification capability with optional identity. */
|
|
10
12
|
export type ResolvedKey = Readonly<{
|
|
11
13
|
algorithm: SigningAlgorithm;
|
|
12
14
|
key?: PublicKey | KeyHandle | Verifier;
|
|
13
15
|
verify?: Verifier;
|
|
14
16
|
identity?: KeyIdentity;
|
|
15
17
|
}>;
|
|
18
|
+
/** Resolve a verification key from signature facts and an operation context. */
|
|
16
19
|
export type KeyResolver = (facts: Readonly<{
|
|
17
20
|
keyid: string | null;
|
|
18
21
|
label: string;
|
|
19
22
|
tag: string | null;
|
|
20
23
|
}>, context: Context) => Result<ResolvedKey | null> | Promise<Result<ResolvedKey | null>>;
|
|
24
|
+
/** Clock, age, skew, and expiration rules for signature freshness. */
|
|
21
25
|
export type Freshness = Readonly<{
|
|
26
|
+
/** Return the current Unix time in seconds. */
|
|
22
27
|
clock: () => number;
|
|
23
28
|
maxAge: number | null;
|
|
24
29
|
skew: number;
|
|
25
30
|
requireExpires: boolean;
|
|
26
31
|
}>;
|
|
32
|
+
/** Digest algorithms and field section required for content verification. */
|
|
27
33
|
export type ContentPolicy = Readonly<{
|
|
28
34
|
kind: "content" | "representation";
|
|
29
35
|
algorithms: readonly string[];
|
|
30
36
|
section: "headers" | "trailers";
|
|
31
37
|
}>;
|
|
38
|
+
/** Nonce namespace, commitment, atomic store, and timeout rules. */
|
|
32
39
|
export type ReplayPolicy = Readonly<{
|
|
33
40
|
identifier: "nonce";
|
|
34
41
|
namespace: Uint8Array;
|
|
42
|
+
/** Derive caller-owned replay commitment bytes from verified facts. */
|
|
35
43
|
commitment: (facts: ReplayFacts, context: Context) => Result<Uint8Array> | Promise<Result<Uint8Array>>;
|
|
36
44
|
store: ReplayStore;
|
|
37
45
|
timeoutMs: number;
|
|
38
46
|
}>;
|
|
47
|
+
/** A branded verification policy with explicit key, freshness, content, and replay rules. */
|
|
39
48
|
export type Policy = Readonly<{
|
|
40
49
|
algorithms: readonly SigningAlgorithm[];
|
|
41
50
|
components: InnerList;
|
|
@@ -48,8 +57,14 @@ export type Policy = Readonly<{
|
|
|
48
57
|
maxSignatures: number;
|
|
49
58
|
}>;
|
|
50
59
|
export declare function fieldSchemas(input: unknown): Result<Readonly<Record<string, Schema>>>;
|
|
60
|
+
/** Check the private brand of a verification policy. */
|
|
51
61
|
export declare function isPolicy(value: unknown): value is Policy;
|
|
62
|
+
/** Internal shared freshness boundary for policies and extension preflight. */
|
|
63
|
+
export declare function normalizeFreshness(input: unknown): Freshness | "not_evaluated";
|
|
64
|
+
/** Snapshot, validate, and brand an explicit verification policy. */
|
|
52
65
|
export declare function createPolicy(input: unknown): Result<Policy>;
|
|
66
|
+
/** Verification policy construction. */
|
|
53
67
|
export declare const Policy: Readonly<{
|
|
68
|
+
/** Snapshot, validate, and brand an explicit verification policy. */
|
|
54
69
|
create: typeof createPolicy;
|
|
55
70
|
}>;
|
package/dist/policy.js
CHANGED
|
@@ -53,9 +53,33 @@ export function fieldSchemas(input) {
|
|
|
53
53
|
return err(bounded(e, "invalid_policy", "input"));
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
/** Check the private brand of a verification policy. */
|
|
56
57
|
export function isPolicy(value) {
|
|
57
58
|
return typeof value === "object" && value !== null && policies.has(value);
|
|
58
59
|
}
|
|
60
|
+
/** Internal shared freshness boundary for policies and extension preflight. */
|
|
61
|
+
export function normalizeFreshness(input) {
|
|
62
|
+
if (input === "not_evaluated")
|
|
63
|
+
return input;
|
|
64
|
+
const f = snapshot(input, [
|
|
65
|
+
"clock",
|
|
66
|
+
"maxAge",
|
|
67
|
+
"skew",
|
|
68
|
+
"requireExpires",
|
|
69
|
+
]);
|
|
70
|
+
ensure(Object.keys(f).length === 4 &&
|
|
71
|
+
typeof f.clock === "function" &&
|
|
72
|
+
(f.maxAge === null || integer(f.maxAge, 1, CLOCK_MAX_SECONDS)) &&
|
|
73
|
+
integer(f.skew, 0, FRESHNESS_MAX_SKEW_SECONDS) &&
|
|
74
|
+
typeof f.requireExpires === "boolean", "invalid_policy", "input");
|
|
75
|
+
return Object.freeze({
|
|
76
|
+
clock: f.clock,
|
|
77
|
+
maxAge: f.maxAge,
|
|
78
|
+
skew: f.skew,
|
|
79
|
+
requireExpires: f.requireExpires,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** Snapshot, validate, and brand an explicit verification policy. */
|
|
59
83
|
export function createPolicy(input) {
|
|
60
84
|
try {
|
|
61
85
|
const a = snapshot(input, [
|
|
@@ -80,26 +104,7 @@ export function createPolicy(input) {
|
|
|
80
104
|
const algorithms = algorithmList(a.algorithms), components = inner(a.components);
|
|
81
105
|
ensure(components.ok && components.value.parameters.length === 0, "invalid_policy", "input");
|
|
82
106
|
ensure(typeof a.keyResolver === "function", "invalid_policy", "input");
|
|
83
|
-
|
|
84
|
-
if (a.freshness !== "not_evaluated") {
|
|
85
|
-
const f = snapshot(a.freshness, [
|
|
86
|
-
"clock",
|
|
87
|
-
"maxAge",
|
|
88
|
-
"skew",
|
|
89
|
-
"requireExpires",
|
|
90
|
-
]);
|
|
91
|
-
ensure(Object.keys(f).length === 4 &&
|
|
92
|
-
typeof f.clock === "function" &&
|
|
93
|
-
(f.maxAge === null || integer(f.maxAge, 1, CLOCK_MAX_SECONDS)) &&
|
|
94
|
-
integer(f.skew, 0, FRESHNESS_MAX_SKEW_SECONDS) &&
|
|
95
|
-
typeof f.requireExpires === "boolean", "invalid_policy", "input");
|
|
96
|
-
freshness = Object.freeze({
|
|
97
|
-
clock: f.clock,
|
|
98
|
-
maxAge: f.maxAge,
|
|
99
|
-
skew: f.skew,
|
|
100
|
-
requireExpires: f.requireExpires,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
107
|
+
const freshness = normalizeFreshness(a.freshness);
|
|
103
108
|
let content = "not_required";
|
|
104
109
|
if (a.content !== "not_required") {
|
|
105
110
|
const c = snapshot(a.content, ["kind", "algorithms", "section"]);
|
|
@@ -171,4 +176,8 @@ export function createPolicy(input) {
|
|
|
171
176
|
return err(bounded(e, "invalid_policy", "input"));
|
|
172
177
|
}
|
|
173
178
|
}
|
|
174
|
-
|
|
179
|
+
/** Verification policy construction. */
|
|
180
|
+
export const Policy = Object.freeze({
|
|
181
|
+
/** Snapshot, validate, and brand an explicit verification policy. */
|
|
182
|
+
create: createPolicy
|
|
183
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Result } from "./result.ts";
|
|
2
|
+
import { type InnerList, type Item } from "./structured-fields/index.ts";
|
|
3
|
+
import type { Verification, FreshnessFacts } from "./verification.ts";
|
|
4
|
+
/** Descriptive extension selection; source-specific enforcement belongs to the extension. */
|
|
5
|
+
export type ProfileMetadataValue = string | boolean | null | number;
|
|
6
|
+
/** A namespaced extension profile name and bounded public metadata. */
|
|
7
|
+
export type ProfileStamp<MetadataKey extends string = never> = Readonly<{
|
|
8
|
+
name: readonly [packageName: string, kind: string];
|
|
9
|
+
}> & Readonly<Record<Exclude<MetadataKey, "name">, ProfileMetadataValue>>;
|
|
10
|
+
/** Paired parsed Signature-Input and Signature dictionaries. */
|
|
11
|
+
export type SignatureDictionaries = readonly [
|
|
12
|
+
readonly (readonly [string, InnerList])[],
|
|
13
|
+
Readonly<Record<string, Item>>
|
|
14
|
+
];
|
|
15
|
+
/** The predicate checks signature semantics, not wire grammar or key trust. */
|
|
16
|
+
declare function validSignatureInput(input: unknown): boolean;
|
|
17
|
+
/** Parse paired Signature-Input and Signature dictionaries within the member limit. */
|
|
18
|
+
declare function dictionaries(message: unknown, max: number): Result<SignatureDictionaries>;
|
|
19
|
+
/** Validate covered components and signature freshness before verification. */
|
|
20
|
+
declare function preflight(input: unknown, components: unknown, extra: "allow" | "reject", freshness: unknown): Promise<Result<FreshnessFacts | "not_evaluated">>;
|
|
21
|
+
/** Verify a selected signature label under an explicit extension profile stamp. */
|
|
22
|
+
declare function verifyLabel<K extends string>(message: unknown, policy: unknown, selectedLabel: unknown, input: unknown, signature: unknown, stamp: ProfileStamp<K>): Promise<Result<Verification>>;
|
|
23
|
+
/** Verify a selected signature label under an explicit extension profile stamp. */
|
|
24
|
+
declare function verifyLabel(message: unknown, policy: unknown, selectedLabel: unknown, input: unknown, signature: unknown, stamp: unknown): Promise<Result<Verification>>;
|
|
25
|
+
/** Unstable extension boundary. Pin the package exactly and keep profile names constant. */
|
|
26
|
+
export declare const Profile: Readonly<{
|
|
27
|
+
/** Check the brand and shape of a signature-input inner list. */
|
|
28
|
+
validSignatureInput: typeof validSignatureInput;
|
|
29
|
+
/** Parse paired Signature-Input and Signature dictionaries within the member limit. */
|
|
30
|
+
dictionaries: typeof dictionaries;
|
|
31
|
+
/** Validate covered components and signature freshness before verification. */
|
|
32
|
+
preflight: typeof preflight;
|
|
33
|
+
/** Verify a selected signature label under an explicit extension profile stamp. */
|
|
34
|
+
verifyLabel: typeof verifyLabel;
|
|
35
|
+
}>;
|
|
36
|
+
export {};
|
package/dist/profile.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { encodeUtf8 } from "./runtime.js";
|
|
2
|
+
import { isMessage, Message } from "./message/index.js";
|
|
3
|
+
import { isPolicy, normalizeFreshness } from "./policy.js";
|
|
4
|
+
import { withDeadline, releaseContext } from "./context.js";
|
|
5
|
+
import { SF_MAX_INTEGER, SIGNATURE_MAX_BYTES, STRUCTURED_FIELDS_LIMITS, PROFILE_MAX_ATOM_CODE_POINTS, PROFILE_MAX_MEMBERS, PROFILE_MAX_VALUE_BYTES } from "./constants.js";
|
|
6
|
+
import { dictionaries as parseDictionaries, validInner, inner, label, ensure } from "./signature-fields.js";
|
|
7
|
+
import { verifyLabel as authenticateLabel, authenticationPreflight } from "./verify.js";
|
|
8
|
+
import { coreError, isRequestSealError } from "./errors/types.js";
|
|
9
|
+
import { ok, err } from "./result.js";
|
|
10
|
+
import { decimalSource } from "./structured-fields/index.js";
|
|
11
|
+
const reserved = new Set(["request_seal", "request-seal", "rfc9421", "web_bot_auth", "web-bot-auth"]);
|
|
12
|
+
function stampString(value, max) {
|
|
13
|
+
if (value.length > max * 2)
|
|
14
|
+
return false;
|
|
15
|
+
let count = 0;
|
|
16
|
+
for (const point of value) {
|
|
17
|
+
const code = point.codePointAt(0);
|
|
18
|
+
if (code >= 0xd800 && code <= 0xdfff || ++count > max)
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
function plain(value) {
|
|
24
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
25
|
+
return false;
|
|
26
|
+
const prototype = Object.getPrototypeOf(value);
|
|
27
|
+
return prototype === Object.prototype || prototype === null;
|
|
28
|
+
}
|
|
29
|
+
/** Read the dynamic metadata vocabulary once, without retaining caller objects. */
|
|
30
|
+
function snapshotStamp(value) {
|
|
31
|
+
ensure(plain(value), "invalid_profile", "input");
|
|
32
|
+
const keys = Reflect.ownKeys(value);
|
|
33
|
+
ensure(keys.length <= PROFILE_MAX_MEMBERS && keys.includes("name") && keys.every(k => typeof k === "string" && stampString(k, PROFILE_MAX_ATOM_CODE_POINTS)), "invalid_profile", "input");
|
|
34
|
+
const ordered = ["name", ...keys.filter(k => k !== "name").sort()];
|
|
35
|
+
const owned = Object.create(null);
|
|
36
|
+
for (const key of ordered)
|
|
37
|
+
owned[key] = value[key];
|
|
38
|
+
const name = owned.name;
|
|
39
|
+
ensure(Array.isArray(name), "invalid_profile", "input");
|
|
40
|
+
const nameKeys = Reflect.ownKeys(name);
|
|
41
|
+
ensure(nameKeys.length === 3 && nameKeys.includes("length") && nameKeys.includes("0") && nameKeys.includes("1") && name.length === 2, "invalid_profile", "input");
|
|
42
|
+
const packageName = name[0], kind = name[1];
|
|
43
|
+
ensure(typeof packageName === "string" && stampString(packageName, PROFILE_MAX_ATOM_CODE_POINTS) && typeof kind === "string" && stampString(kind, PROFILE_MAX_ATOM_CODE_POINTS) && !reserved.has(packageName), "invalid_profile", "input");
|
|
44
|
+
for (const key of keys) {
|
|
45
|
+
if (key === "name")
|
|
46
|
+
continue;
|
|
47
|
+
const v = owned[key];
|
|
48
|
+
ensure(v === null || typeof v === "boolean" ||
|
|
49
|
+
(typeof v === "number" && Number.isSafeInteger(v) && Math.abs(v) <= SF_MAX_INTEGER) ||
|
|
50
|
+
(typeof v === "string" && stampString(v, PROFILE_MAX_VALUE_BYTES) && encodeUtf8(v).length <= PROFILE_MAX_VALUE_BYTES), "invalid_profile", "input");
|
|
51
|
+
if (typeof v === "number" && Object.is(v, -0))
|
|
52
|
+
owned[key] = 0;
|
|
53
|
+
}
|
|
54
|
+
owned.name = Object.freeze([packageName, kind]);
|
|
55
|
+
// Sorted insertion gives equal stamps one key order; JavaScript lists array-index keys first.
|
|
56
|
+
return Object.freeze(owned);
|
|
57
|
+
}
|
|
58
|
+
function protectedError(e) {
|
|
59
|
+
return isRequestSealError(e) ? e : coreError("invalid_policy", "input");
|
|
60
|
+
}
|
|
61
|
+
/** The predicate checks signature semantics, not wire grammar or key trust. */
|
|
62
|
+
function validSignatureInput(input) {
|
|
63
|
+
try {
|
|
64
|
+
return validInner(input);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Parse paired Signature-Input and Signature dictionaries within the member limit. */
|
|
71
|
+
function dictionaries(message, max) {
|
|
72
|
+
try {
|
|
73
|
+
ensure(isMessage(message) && Number.isSafeInteger(max) && max >= 1 && max <= STRUCTURED_FIELDS_LIMITS.maxMembers, "invalid_policy", "input");
|
|
74
|
+
const [inputs, signatures] = paired(message, max);
|
|
75
|
+
const entries = Object.freeze([...inputs].map(([k, v]) => Object.freeze([k, v])));
|
|
76
|
+
const lookup = Object.create(null);
|
|
77
|
+
for (const [key, value] of signatures)
|
|
78
|
+
lookup[key] = value;
|
|
79
|
+
return ok(Object.freeze([entries, Object.freeze(lookup)]));
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
return err(protectedError(e));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function paired(message, max) {
|
|
86
|
+
const values = parseDictionaries(message, max);
|
|
87
|
+
const [inputs, signatures] = values;
|
|
88
|
+
ensure(inputs.size === signatures.size && [...inputs.keys()].every(k => signatures.has(k)), "label_mismatch", "fields");
|
|
89
|
+
return values;
|
|
90
|
+
}
|
|
91
|
+
/** Validate covered components and signature freshness before verification. */
|
|
92
|
+
async function preflight(input, components, extra, freshness) {
|
|
93
|
+
try {
|
|
94
|
+
const required = inner(components);
|
|
95
|
+
if (!required.ok)
|
|
96
|
+
throw required.error.reason === "limit" ? coreError("limit", "input") : coreError("invalid_signature_input", "fields");
|
|
97
|
+
ensure(extra === "allow" || extra === "reject", "invalid_policy", "input");
|
|
98
|
+
const context = withDeadline();
|
|
99
|
+
if (!context.ok)
|
|
100
|
+
throw context.error;
|
|
101
|
+
try {
|
|
102
|
+
return ok(await authenticationPreflight(input, required.value, extra, () => normalizeFreshness(freshness), context.value));
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
releaseContext(context.value);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
return err(protectedError(e));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** Expected parser structure bounds traversal; unknown keys cannot hide extra values.
|
|
113
|
+
* Ordered parameters/components and decoded byte equality mirror the reference values.
|
|
114
|
+
* Each caller property is read once, independent of object insertion order or identity.
|
|
115
|
+
*/
|
|
116
|
+
function equalParsed(actual, expected) {
|
|
117
|
+
if (expected === null || typeof expected !== "object")
|
|
118
|
+
return actual === expected;
|
|
119
|
+
if (expected instanceof Uint8Array) {
|
|
120
|
+
if (!(actual instanceof Uint8Array) || actual.length !== expected.length)
|
|
121
|
+
return false;
|
|
122
|
+
const bytes = new Uint8Array(actual);
|
|
123
|
+
return bytes.every((b, i) => b === expected[i]);
|
|
124
|
+
}
|
|
125
|
+
if (Array.isArray(expected)) {
|
|
126
|
+
if (!Array.isArray(actual))
|
|
127
|
+
return false;
|
|
128
|
+
const keys = Reflect.ownKeys(actual);
|
|
129
|
+
if (actual.length !== expected.length || keys.length !== expected.length + 1 || !keys.includes("length"))
|
|
130
|
+
return false;
|
|
131
|
+
for (let i = 0; i < expected.length; i++)
|
|
132
|
+
if (!Object.hasOwn(actual, String(i)) || !equalParsed(actual[i], expected[i]))
|
|
133
|
+
return false;
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
if (!plain(actual))
|
|
137
|
+
return false;
|
|
138
|
+
const keys = Reflect.ownKeys(actual), expectedKeys = Object.keys(expected);
|
|
139
|
+
if (keys.length !== expectedKeys.length || !keys.every(k => typeof k === "string" && Object.hasOwn(expected, k)))
|
|
140
|
+
return false;
|
|
141
|
+
const fields = Object.create(null);
|
|
142
|
+
for (const key of expectedKeys)
|
|
143
|
+
fields[key] = actual[key];
|
|
144
|
+
const parsed = expected;
|
|
145
|
+
if (parsed.kind === "decimal") {
|
|
146
|
+
if (fields.kind !== "decimal")
|
|
147
|
+
return false;
|
|
148
|
+
const selected = decimalParts(actual, fields.value);
|
|
149
|
+
const authoritative = decimalParts(expected, parsed.value, decimalSource(actual) !== undefined);
|
|
150
|
+
return selected !== null && authoritative !== null && selected[0] === authoritative[0] && selected[1] === authoritative[1];
|
|
151
|
+
}
|
|
152
|
+
for (const key of expectedKeys)
|
|
153
|
+
if (!equalParsed(fields[key], parsed[key]))
|
|
154
|
+
return false;
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
/** Parsed decimals retain source coefficient/scale separately from numeric normalization.
|
|
158
|
+
* Leading zero and negative-zero spellings do not change the reference integer coefficient.
|
|
159
|
+
*/
|
|
160
|
+
function decimalParts(node, value, useSource = true) {
|
|
161
|
+
if (!plain(value))
|
|
162
|
+
return null;
|
|
163
|
+
const keys = Reflect.ownKeys(value);
|
|
164
|
+
if (keys.length !== 2 || !keys.includes("scaled") || !keys.includes("scale"))
|
|
165
|
+
return null;
|
|
166
|
+
const scaled = value.scaled, scale = value.scale;
|
|
167
|
+
if (!(typeof scaled === "bigint" || typeof scaled === "number" && Number.isSafeInteger(scaled)) || typeof scale !== "number" || !Number.isSafeInteger(scale) || scale < 1 || scale > 3)
|
|
168
|
+
return null;
|
|
169
|
+
const source = useSource ? decimalSource(node) : undefined;
|
|
170
|
+
return source === undefined ? [BigInt(scaled), scale] : [BigInt(source.replace(".", "")), source.length - source.indexOf(".") - 1];
|
|
171
|
+
}
|
|
172
|
+
function signatureSnapshot(value) {
|
|
173
|
+
ensure(plain(value), "invalid_signature_field", "fields");
|
|
174
|
+
const keys = Reflect.ownKeys(value);
|
|
175
|
+
ensure(keys.length === 3 && keys.every(k => typeof k === "string" && ["kind", "value", "parameters"].includes(k)), "invalid_signature_field", "fields");
|
|
176
|
+
const kind = value.kind, bare = value.value, parameters = value.parameters;
|
|
177
|
+
ensure(kind === "item" && plain(bare), "invalid_signature_field", "fields");
|
|
178
|
+
const bareKeys = Reflect.ownKeys(bare);
|
|
179
|
+
ensure(bareKeys.length === 2 && bareKeys.includes("kind") && bareKeys.includes("value"), "invalid_signature_field", "fields");
|
|
180
|
+
const bareKind = bare.kind, bytes = bare.value;
|
|
181
|
+
ensure(bareKind === "bytes" && bytes instanceof Uint8Array && bytes.length >= 1 && bytes.length <= SIGNATURE_MAX_BYTES, "invalid_signature_field", "fields");
|
|
182
|
+
return { kind: "item", value: { kind: "bytes", value: new Uint8Array(bytes) }, parameters: parameters };
|
|
183
|
+
}
|
|
184
|
+
/** Verify a selected signature label under an explicit extension profile stamp. */
|
|
185
|
+
async function verifyLabel(message, policy, selectedLabel, input, signature, stamp) {
|
|
186
|
+
try {
|
|
187
|
+
const profile = snapshotStamp(stamp);
|
|
188
|
+
ensure(isPolicy(policy), "invalid_policy", "input");
|
|
189
|
+
ensure(label(selectedLabel), "invalid_options", "input");
|
|
190
|
+
ensure(isMessage(message), "invalid_message", "input");
|
|
191
|
+
const validated = Message.create(message);
|
|
192
|
+
ensure(validated.ok, "invalid_message", "input");
|
|
193
|
+
const selectedSignature = signatureSnapshot(signature);
|
|
194
|
+
const [inputs, signatures] = paired(validated.value, policy.maxSignatures);
|
|
195
|
+
ensure(inputs.has(selectedLabel), "unknown_label", "fields");
|
|
196
|
+
const selectedInput = inputs.get(selectedLabel);
|
|
197
|
+
ensure(equalParsed(input, selectedInput), "invalid_signature_input", "fields");
|
|
198
|
+
ensure(equalParsed(selectedSignature, signatures.get(selectedLabel)), "invalid_signature_field", "fields");
|
|
199
|
+
return await authenticateLabel(validated.value, policy, { label: selectedLabel }, undefined, { input: selectedInput, signature: signatures.get(selectedLabel), baseCache: new Map() }, profile);
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
return err(protectedError(e));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/** Unstable extension boundary. Pin the package exactly and keep profile names constant. */
|
|
206
|
+
export const Profile = Object.freeze({
|
|
207
|
+
/** Check the brand and shape of a signature-input inner list. */
|
|
208
|
+
validSignatureInput,
|
|
209
|
+
/** Parse paired Signature-Input and Signature dictionaries within the member limit. */
|
|
210
|
+
dictionaries,
|
|
211
|
+
/** Validate covered components and signature freshness before verification. */
|
|
212
|
+
preflight,
|
|
213
|
+
/** Verify a selected signature label under an explicit extension profile stamp. */
|
|
214
|
+
verifyLabel
|
|
215
|
+
});
|
package/dist/public_key.d.ts
CHANGED
|
@@ -1,23 +1,32 @@
|
|
|
1
1
|
import { type Result } from './result.ts';
|
|
2
2
|
import { type Jwk } from './custody/jwk.ts';
|
|
3
|
+
/** Check the private brand of a public key. */
|
|
3
4
|
export declare function isPublicKey(value: unknown): value is PublicKey;
|
|
4
5
|
/** Public components only. Metadata restrictions survive every use and export. */
|
|
5
6
|
export declare class PublicKey {
|
|
6
7
|
#private;
|
|
7
8
|
private constructor();
|
|
9
|
+
/** Import public JWK, PEM, or SPKI material without discarding metadata restrictions. */
|
|
8
10
|
static import(material: unknown, format?: 'jwk' | 'pem' | 'spki' | 'der'): Promise<Result<PublicKey>>;
|
|
11
|
+
/** Export a defensive copy of the public JWK and its metadata restrictions. */
|
|
9
12
|
jwk(): Result<Jwk>;
|
|
13
|
+
/** Export public SPKI bytes when metadata restrictions permit a lossless export. */
|
|
10
14
|
spki(): Result<Uint8Array<ArrayBuffer>>;
|
|
15
|
+
/** Export public SPKI PEM when metadata restrictions permit a lossless export. */
|
|
11
16
|
pem(): Result<string>;
|
|
17
|
+
/** Compute the public key thumbprint defined by RFC 7638. */
|
|
12
18
|
thumbprint(): Promise<Result<string>>;
|
|
13
19
|
/** Internal equivalence ignores PSS-only type and JWK metadata. */
|
|
14
20
|
identityBytes(): Result<Uint8Array<ArrayBuffer>>;
|
|
21
|
+
/** Check compatibility and metadata permission for the selected algorithm. */
|
|
15
22
|
bind(algorithm: unknown): Result<true>;
|
|
16
|
-
|
|
23
|
+
/** Verify a signature with bounded input and preserved key metadata restrictions. */
|
|
24
|
+
verify(algorithm: unknown, signature: unknown, data: unknown, options?: unknown): Promise<Result<true>>;
|
|
25
|
+
/** Return a bounded public inspection view without retained cryptographic bytes. */
|
|
17
26
|
toJSON(): Readonly<{
|
|
18
27
|
kind: string;
|
|
19
28
|
pssOnly: boolean;
|
|
20
29
|
}>;
|
|
21
30
|
}
|
|
22
|
-
export declare function verifyPublicKey(key: PublicKey, algorithm: unknown, signature: unknown, data: unknown): Promise<Result<true>>;
|
|
31
|
+
export declare function verifyPublicKey(key: PublicKey, algorithm: unknown, signature: unknown, data: unknown, maximum?: number): Promise<Result<true>>;
|
|
23
32
|
export declare function bindPublicKey(key: PublicKey, algorithm: unknown): Result<true>;
|
package/dist/public_key.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
import { denoInspection } from './brand.js';
|
|
2
|
+
import { byteLimit } from './crypto/options.js';
|
|
3
|
+
import { CRYPTO_DEFAULT_MAX_BYTES } from './constants.js';
|
|
1
4
|
import { encodeUtf8 } from './runtime.js';
|
|
2
5
|
import { ok, err } from './result.js';
|
|
3
6
|
import { cryptoError } from './errors/types.js';
|
|
4
7
|
import { encodeBase64Url } from './bytes.js';
|
|
5
8
|
import { resolveAlgorithm, importParameters, } from './crypto/algorithm.js';
|
|
6
9
|
import * as subtle from './crypto/subtle.js';
|
|
7
|
-
import { verifyKey } from './crypto/primitive.js';
|
|
10
|
+
import { boundedData, verifyKey } from './crypto/primitive.js';
|
|
8
11
|
import { decodeKey, publicSpki, writePem } from './custody/der.js';
|
|
9
12
|
import { snapshotJwk, publicMembers, compatible, permitted, } from './custody/jwk.js';
|
|
13
|
+
const verifiers = new WeakMap();
|
|
10
14
|
const branded = new WeakSet();
|
|
11
15
|
const token = Object.freeze({});
|
|
16
|
+
/** Check the private brand of a public key. */
|
|
12
17
|
export function isPublicKey(value) {
|
|
13
18
|
return typeof value === 'object' && value !== null && branded.has(value);
|
|
14
19
|
}
|
|
@@ -23,9 +28,11 @@ export class PublicKey {
|
|
|
23
28
|
this.#jwk = jwk;
|
|
24
29
|
this.#pssOnly = pssOnly;
|
|
25
30
|
this.#spki = new Uint8Array(spki);
|
|
31
|
+
verifiers.set(this, this.#verify.bind(this));
|
|
26
32
|
branded.add(this);
|
|
27
33
|
Object.freeze(this);
|
|
28
34
|
}
|
|
35
|
+
/** Import public JWK, PEM, or SPKI material without discarding metadata restrictions. */
|
|
29
36
|
static async import(material, format = 'jwk') {
|
|
30
37
|
try {
|
|
31
38
|
let jwk, pssOnly = false;
|
|
@@ -76,16 +83,19 @@ export class PublicKey {
|
|
|
76
83
|
return err(cryptoError('invalid_key'));
|
|
77
84
|
}
|
|
78
85
|
}
|
|
86
|
+
/** Export a defensive copy of the public JWK and its metadata restrictions. */
|
|
79
87
|
jwk() {
|
|
80
88
|
return this.#pssOnly
|
|
81
89
|
? err(cryptoError('unsupported_format'))
|
|
82
90
|
: ok(this.#jwk);
|
|
83
91
|
}
|
|
92
|
+
/** Export public SPKI bytes when metadata restrictions permit a lossless export. */
|
|
84
93
|
spki() {
|
|
85
94
|
return ['alg', 'use', 'key_ops'].some((k) => Object.hasOwn(this.#jwk, k))
|
|
86
95
|
? err(cryptoError('unsupported_format'))
|
|
87
96
|
: ok(new Uint8Array(this.#spki));
|
|
88
97
|
}
|
|
98
|
+
/** Export public SPKI PEM when metadata restrictions permit a lossless export. */
|
|
89
99
|
pem() {
|
|
90
100
|
const r = this.spki();
|
|
91
101
|
if (!r.ok)
|
|
@@ -93,6 +103,7 @@ export class PublicKey {
|
|
|
93
103
|
const p = writePem('PUBLIC KEY', r.value);
|
|
94
104
|
return p.ok ? p : err(cryptoError('invalid_key'));
|
|
95
105
|
}
|
|
106
|
+
/** Compute the public key thumbprint defined by RFC 7638. */
|
|
96
107
|
async thumbprint() {
|
|
97
108
|
try {
|
|
98
109
|
return ok(encodeBase64Url(new Uint8Array(await subtle.digest('SHA-256', encodeUtf8(JSON.stringify(publicMembers(this.#jwk)))))));
|
|
@@ -105,6 +116,7 @@ export class PublicKey {
|
|
|
105
116
|
identityBytes() {
|
|
106
117
|
return ok(encodeUtf8(JSON.stringify(publicMembers(this.#jwk))));
|
|
107
118
|
}
|
|
119
|
+
/** Check compatibility and metadata permission for the selected algorithm. */
|
|
108
120
|
bind(algorithm) {
|
|
109
121
|
const resolved = resolveAlgorithm(algorithm, true);
|
|
110
122
|
if (!resolved.ok)
|
|
@@ -118,38 +130,49 @@ export class PublicKey {
|
|
|
118
130
|
? ok(true)
|
|
119
131
|
: err(cryptoError('key_mismatch'));
|
|
120
132
|
}
|
|
121
|
-
|
|
133
|
+
/** Verify a signature with bounded input and preserved key metadata restrictions. */
|
|
134
|
+
async verify(algorithm, signature, data, options = {}) {
|
|
135
|
+
const maximum = byteLimit(options);
|
|
136
|
+
if (!maximum.ok)
|
|
137
|
+
return maximum;
|
|
138
|
+
return this.#verify(algorithm, signature, data, maximum.value);
|
|
139
|
+
}
|
|
140
|
+
async #verify(algorithm, signature, data, maximum) {
|
|
122
141
|
const resolved = resolveAlgorithm(algorithm);
|
|
123
142
|
if (!resolved.ok)
|
|
124
143
|
return resolved;
|
|
125
144
|
const spec = resolved.value;
|
|
145
|
+
// Same order as Crypto.verify: options, algorithm, data bound, then key compatibility.
|
|
146
|
+
if (!boundedData(data, maximum))
|
|
147
|
+
return err(cryptoError('invalid_data'));
|
|
126
148
|
try {
|
|
127
149
|
if (!compatible(this.#jwk, spec, this.#pssOnly) ||
|
|
128
150
|
!permitted(this.#jwk, spec, 'verify'))
|
|
129
151
|
return err(cryptoError('key_mismatch'));
|
|
130
152
|
const key = await subtle.importKey('jwk', publicMembers(this.#jwk), importParameters(spec), false, ['verify']);
|
|
131
|
-
return await verifyKey(spec, key, signature, data);
|
|
153
|
+
return await verifyKey(spec, key, signature, data, maximum);
|
|
132
154
|
}
|
|
133
155
|
catch {
|
|
134
156
|
return err(cryptoError('invalid_key'));
|
|
135
157
|
}
|
|
136
158
|
}
|
|
159
|
+
/** Return a bounded public inspection view without retained cryptographic bytes. */
|
|
137
160
|
toJSON() {
|
|
138
161
|
return Object.freeze({ kind: 'PublicKey', pssOnly: this.#pssOnly });
|
|
139
162
|
}
|
|
140
163
|
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
141
164
|
return this.toJSON();
|
|
142
165
|
}
|
|
143
|
-
[Symbol.for('Deno.customInspect')]() {
|
|
144
|
-
return this.toJSON();
|
|
166
|
+
[Symbol.for('Deno.customInspect')](render) {
|
|
167
|
+
return denoInspection(render, this.toJSON());
|
|
145
168
|
}
|
|
146
169
|
}
|
|
147
170
|
Object.freeze(PublicKey.prototype);
|
|
148
171
|
Object.freeze(PublicKey);
|
|
149
|
-
const publicVerify = Function.prototype.call.bind(PublicKey.prototype.verify);
|
|
150
172
|
const publicBind = Function.prototype.call.bind(PublicKey.prototype.bind);
|
|
151
|
-
export function verifyPublicKey(key, algorithm, signature, data) {
|
|
152
|
-
|
|
173
|
+
export function verifyPublicKey(key, algorithm, signature, data, maximum = CRYPTO_DEFAULT_MAX_BYTES) {
|
|
174
|
+
const verify = verifiers.get(key);
|
|
175
|
+
return verify ? verify(algorithm, signature, data, maximum) : Promise.resolve(err(cryptoError('invalid_key')));
|
|
153
176
|
}
|
|
154
177
|
export function bindPublicKey(key, algorithm) {
|
|
155
178
|
return publicBind(key, algorithm);
|