@polyjose/node 0.0.0-stage → 0.1.2
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/LICENSE +21 -0
- package/README.md +98 -2
- package/THIRD-PARTY-NOTICES.txt +16006 -0
- package/addon-sha256.json +10 -0
- package/backend.d.ts +86 -0
- package/backend.js +253 -0
- package/facade/api.d.ts +123 -0
- package/facade/api.js +567 -0
- package/facade/backend.d.ts +159 -0
- package/facade/backend.js +19 -0
- package/facade/error-codes.d.ts +4 -0
- package/facade/error-codes.js +22 -0
- package/facade/normalize.d.ts +134 -0
- package/facade/normalize.js +380 -0
- package/facade/types.d.ts +206 -0
- package/facade/types.js +14 -0
- package/index.d.ts +11 -0
- package/index.js +12 -0
- package/package.json +60 -4
- package/polyjose.darwin-arm64.node +0 -0
- package/polyjose.darwin-x64.node +0 -0
- package/polyjose.linux-arm64-gnu.node +0 -0
- package/polyjose.linux-arm64-musl.node +0 -0
- package/polyjose.linux-x64-gnu.node +0 -0
- package/polyjose.linux-x64-musl.node +0 -0
- package/polyjose.win32-arm64-msvc.node +0 -0
- package/polyjose.win32-x64-msvc.node +0 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input and output normalisation of the facade: idiom only (ADR 0030 §1 host facade row, §2;
|
|
3
|
+
* ADR 0031 §1). No policy, no defaults, no JOSE validation: the core decides all of that.
|
|
4
|
+
*
|
|
5
|
+
* Every failure here is a host `TypeError`, never a `PolyjoseError` (ADR 0030 E2: host type
|
|
6
|
+
* errors stay outside the code contract). A message names the argument and the expected type,
|
|
7
|
+
* never the value, which may be key material (ADR 0030 §5, "no input in errors").
|
|
8
|
+
*/
|
|
9
|
+
/** `null` and `undefined` both mean "absent" (napi throws on `null`, research §3). */
|
|
10
|
+
export function isAbsent(v) {
|
|
11
|
+
return v === undefined || v === null;
|
|
12
|
+
}
|
|
13
|
+
function fail(at, expected) {
|
|
14
|
+
return new TypeError(`polyjose: ${at} must be ${expected}`);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `%TypedArray%.prototype[Symbol.toStringTag]` reads the internal [[TypedArrayName]] slot, so
|
|
18
|
+
* it can't be spoofed by an object's own `Symbol.toStringTag`, and it works across realms.
|
|
19
|
+
*/
|
|
20
|
+
const typedArrayName = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Uint8Array.prototype), Symbol.toStringTag)?.get;
|
|
21
|
+
/** A `Uint8Array`, including a Node `Buffer` (a subclass). */
|
|
22
|
+
export function isUint8Array(v) {
|
|
23
|
+
return ArrayBuffer.isView(v) && typedArrayName?.call(v) === "Uint8Array";
|
|
24
|
+
}
|
|
25
|
+
/** An object literal (or `Object.create(null)`), not an array or class instance. */
|
|
26
|
+
export function isPlainObject(v) {
|
|
27
|
+
if (typeof v !== "object" || v === null || Array.isArray(v))
|
|
28
|
+
return false;
|
|
29
|
+
const proto = Object.getPrototypeOf(v);
|
|
30
|
+
return proto === Object.prototype || proto === null;
|
|
31
|
+
}
|
|
32
|
+
export function str(v, at) {
|
|
33
|
+
if (typeof v !== "string")
|
|
34
|
+
throw fail(at, "a string");
|
|
35
|
+
return v;
|
|
36
|
+
}
|
|
37
|
+
export function optStr(v, at) {
|
|
38
|
+
return isAbsent(v) ? undefined : str(v, at);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A byte input, as a fresh copy (ADR 0030 §7; 0013 B06). The facade calls this synchronously,
|
|
42
|
+
* when the operation is called and before it awaits the backend's lazy `ready()` (ADR 0031 §3),
|
|
43
|
+
* so a caller that reuses its array after starting a call can't change what is signed or
|
|
44
|
+
* encrypted. The copy is a plain `Uint8Array` holding exactly the view's bytes (a `Buffer` or a
|
|
45
|
+
* subarray shares no memory with it) and is pushed onto `copies`, which the facade wipes once the
|
|
46
|
+
* backend call settles; the caller's own array is never wiped. A view over a detached buffer
|
|
47
|
+
* can't be copied and is a TypeError.
|
|
48
|
+
*/
|
|
49
|
+
export function bytes(v, at, copies) {
|
|
50
|
+
if (!isUint8Array(v))
|
|
51
|
+
throw fail(at, "a Uint8Array");
|
|
52
|
+
let copy;
|
|
53
|
+
try {
|
|
54
|
+
// The TypedArray constructor reads the internal slots, not `length` or an iterator.
|
|
55
|
+
copy = new Uint8Array(v);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw fail(at, "a Uint8Array over an attached ArrayBuffer");
|
|
59
|
+
}
|
|
60
|
+
append(copies, copy);
|
|
61
|
+
return copy;
|
|
62
|
+
}
|
|
63
|
+
export function optBytes(v, at, copies) {
|
|
64
|
+
return isAbsent(v) ? undefined : bytes(v, at, copies);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Wipes the copies {@link bytes} made with `fill(0)` (ADR 0030 §7). JS gives no guarantee that
|
|
68
|
+
* the engine kept no other copy (ADR 0030 §7 rule 4); this removes the facade's own.
|
|
69
|
+
*/
|
|
70
|
+
export function wipe(copies) {
|
|
71
|
+
for (const copy of copies)
|
|
72
|
+
copy.fill(0);
|
|
73
|
+
}
|
|
74
|
+
export function bool(v, at) {
|
|
75
|
+
if (typeof v !== "boolean")
|
|
76
|
+
throw fail(at, "a boolean");
|
|
77
|
+
return v;
|
|
78
|
+
}
|
|
79
|
+
export function optBool(v, at) {
|
|
80
|
+
return isAbsent(v) ? undefined : bool(v, at);
|
|
81
|
+
}
|
|
82
|
+
/** The largest `u32`: sizes and counts are `u32` in the surface (ADR 0030 §2). */
|
|
83
|
+
const U32_MAX = 0xffff_ffff;
|
|
84
|
+
/**
|
|
85
|
+
* A size or count: an integer in 0..=2^32−1. Checked here because wasm-bindgen would coerce
|
|
86
|
+
* `-1` to `4294967295` (research §3).
|
|
87
|
+
*/
|
|
88
|
+
export function u32(v, at) {
|
|
89
|
+
if (typeof v !== "number" || !Number.isInteger(v) || v < 0 || v > U32_MAX) {
|
|
90
|
+
throw fail(at, "an integer from 0 to 4294967295");
|
|
91
|
+
}
|
|
92
|
+
return v;
|
|
93
|
+
}
|
|
94
|
+
export function optU32(v, at) {
|
|
95
|
+
return isAbsent(v) ? undefined : u32(v, at);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Whole seconds since the epoch (RFC 7519 §2), a safe integer: the NumericDate and clock range
|
|
99
|
+
* ±(2^53 − 1) of ADR 0027 §2 and ADR 0028 §1, which a JS number holds exactly.
|
|
100
|
+
*/
|
|
101
|
+
export function optSeconds(v, at) {
|
|
102
|
+
if (isAbsent(v))
|
|
103
|
+
return undefined;
|
|
104
|
+
if (typeof v !== "number" || !Number.isSafeInteger(v))
|
|
105
|
+
throw fail(at, "a safe integer (whole seconds)");
|
|
106
|
+
return v;
|
|
107
|
+
}
|
|
108
|
+
/** An array literal: an `Array` whose prototype is `Array.prototype` or `null`, like {@link isPlainObject}. */
|
|
109
|
+
function isPlainArray(v) {
|
|
110
|
+
if (!Array.isArray(v))
|
|
111
|
+
return false;
|
|
112
|
+
const proto = Object.getPrototypeOf(v);
|
|
113
|
+
return proto === Array.prototype || proto === null;
|
|
114
|
+
}
|
|
115
|
+
/** A value {@link jsonText} can't serialise: caught by {@link json} and reported without the value. */
|
|
116
|
+
class NotJsonData extends Error {
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The JSON text of plain data (RFC 8259 §2–§7), read from the caller's own members only
|
|
120
|
+
* (0013 F09, security audit M1 follow-up). The output equals `JSON.stringify(v)` for plain data
|
|
121
|
+
* in an unpolluted realm: the same member order (`Object.keys`, ECMA-262 EnumerableOwnProperties),
|
|
122
|
+
* escapes and number spelling (both from `JSON.stringify` of a primitive), a member whose value
|
|
123
|
+
* is `undefined` left out and an `undefined` element written as `null`, `NaN` and `±Infinity` as
|
|
124
|
+
* `null`.
|
|
125
|
+
*
|
|
126
|
+
* It differs from `JSON.stringify` exactly where that reads the host's global state or calls
|
|
127
|
+
* the caller's code:
|
|
128
|
+
* - **no `toJSON` is called, at any depth**: neither an inherited one (a polluted
|
|
129
|
+
* `Object.prototype.toJSON`, `Array.prototype.toJSON` or `BigInt.prototype.toJSON` could
|
|
130
|
+
* otherwise replace the claims, key or header being sent) nor an own one;
|
|
131
|
+
* - **an array hole is not data**: `JSON.stringify` reads it through `Array.prototype`, where a
|
|
132
|
+
* pollution bug could have put a key into a JWK Set; here it is a TypeError, as in
|
|
133
|
+
* {@link list};
|
|
134
|
+
* - **only JSON data is accepted** (ADR 0030 §2 "a plain object", the `JsonValue` type):
|
|
135
|
+
* {@link isPlainObject} objects, {@link isPlainArray} arrays, strings, numbers, booleans and
|
|
136
|
+
* `null`. A `Date`, `Map`, class instance, boxed primitive, typed array, function, symbol or
|
|
137
|
+
* BigInt is a TypeError wherever it appears, never coerced by its own or an inherited method.
|
|
138
|
+
*
|
|
139
|
+
* Each own member is read once (a getter runs once, as in {@link record}), and one an earlier
|
|
140
|
+
* getter deleted is skipped, never read through the prototype. A cycle is a TypeError; the same
|
|
141
|
+
* object reached twice without a cycle is written twice, as `JSON.stringify` does.
|
|
142
|
+
*
|
|
143
|
+
* `JSON.stringify` of a string or number is safe: ECMA-262 SerializeJSONProperty looks up
|
|
144
|
+
* `toJSON` only on an Object or a BigInt, so a primitive is never coerced through a prototype.
|
|
145
|
+
* The output is built with `+` only, so no `Array.prototype` method is involved.
|
|
146
|
+
*/
|
|
147
|
+
function jsonText(v, ancestors) {
|
|
148
|
+
if (v === null)
|
|
149
|
+
return "null";
|
|
150
|
+
switch (typeof v) {
|
|
151
|
+
case "string":
|
|
152
|
+
return JSON.stringify(v);
|
|
153
|
+
case "number":
|
|
154
|
+
return Number.isFinite(v) ? JSON.stringify(v) : "null";
|
|
155
|
+
case "boolean":
|
|
156
|
+
return v ? "true" : "false";
|
|
157
|
+
case "undefined":
|
|
158
|
+
return undefined;
|
|
159
|
+
case "object":
|
|
160
|
+
break;
|
|
161
|
+
default:
|
|
162
|
+
// function, symbol, bigint: not JSON data.
|
|
163
|
+
throw new NotJsonData();
|
|
164
|
+
}
|
|
165
|
+
if (ancestors.has(v))
|
|
166
|
+
throw new NotJsonData();
|
|
167
|
+
if (isPlainArray(v)) {
|
|
168
|
+
ancestors.add(v);
|
|
169
|
+
const n = v.length;
|
|
170
|
+
let out = "[";
|
|
171
|
+
for (let i = 0; i < n; i++) {
|
|
172
|
+
if (!Object.hasOwn(v, i))
|
|
173
|
+
throw new NotJsonData();
|
|
174
|
+
if (i > 0)
|
|
175
|
+
out += ",";
|
|
176
|
+
out += jsonText(v[i], ancestors) ?? "null";
|
|
177
|
+
}
|
|
178
|
+
ancestors.delete(v);
|
|
179
|
+
return out + "]";
|
|
180
|
+
}
|
|
181
|
+
if (isPlainObject(v)) {
|
|
182
|
+
ancestors.add(v);
|
|
183
|
+
let out = "{";
|
|
184
|
+
let first = true;
|
|
185
|
+
for (const key of Object.keys(v)) {
|
|
186
|
+
if (!Object.hasOwn(v, key))
|
|
187
|
+
continue;
|
|
188
|
+
const text = jsonText(v[key], ancestors);
|
|
189
|
+
if (text === undefined)
|
|
190
|
+
continue;
|
|
191
|
+
out += (first ? "" : ",") + JSON.stringify(key) + ":" + text;
|
|
192
|
+
first = false;
|
|
193
|
+
}
|
|
194
|
+
ancestors.delete(v);
|
|
195
|
+
return out + "}";
|
|
196
|
+
}
|
|
197
|
+
throw new NotJsonData();
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* JSON text (ADR 0030 §2): a string is passed through unchanged (so member order and number
|
|
201
|
+
* spelling reach the core's strict parser as written); a plain object is serialised by
|
|
202
|
+
* {@link jsonText}, the `JSON.stringify` text of its own plain data, with no `toJSON` consulted
|
|
203
|
+
* and nothing read through a prototype (0013 F09).
|
|
204
|
+
*/
|
|
205
|
+
export function json(v, at) {
|
|
206
|
+
if (typeof v === "string")
|
|
207
|
+
return v;
|
|
208
|
+
if (!isPlainObject(v))
|
|
209
|
+
throw fail(at, "JSON text or a plain object");
|
|
210
|
+
try {
|
|
211
|
+
return jsonText(v, new Set()) ?? "{}";
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// Not JSON data, a cycle, a throwing getter or a nesting too deep for the stack. The message
|
|
215
|
+
// names the argument only: a value or member name may be key material (ADR 0030 §5).
|
|
216
|
+
throw fail(at, "JSON text or a plain object of JSON data (plain objects, arrays without holes, strings, numbers, booleans, null)");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
export function optJson(v, at) {
|
|
220
|
+
return isAbsent(v) ? undefined : json(v, at);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* A list, item by item, copied. Only the array's own elements below its `length` are read: a
|
|
224
|
+
* hole is a TypeError naming its index, never read through `Array.prototype`, where a pollution
|
|
225
|
+
* bug elsewhere in the host app could have put a value (0013 F04, security audit M1).
|
|
226
|
+
*/
|
|
227
|
+
export function list(v, at, item) {
|
|
228
|
+
if (!Array.isArray(v))
|
|
229
|
+
throw fail(at, "an array");
|
|
230
|
+
const n = v.length;
|
|
231
|
+
const out = [];
|
|
232
|
+
for (let i = 0; i < n; i++) {
|
|
233
|
+
if (!Object.hasOwn(v, i))
|
|
234
|
+
throw fail(`${at}[${i}]`, "present: an array without holes");
|
|
235
|
+
append(out, item(v[i], `${at}[${i}]`));
|
|
236
|
+
}
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
export function strList(v, at) {
|
|
240
|
+
return list(v, at, str);
|
|
241
|
+
}
|
|
242
|
+
export function optStrList(v, at) {
|
|
243
|
+
return isAbsent(v) ? undefined : strList(v, at);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* An allow-list (`allowedAlgs`, `allowedEnc`). Absent is passed as `[]`, which the core refuses
|
|
247
|
+
* with `AlgorithmNotAllowed`, the code an empty or `none`-only list gets on every platform
|
|
248
|
+
* (ADR 0030 §3). This is not a default list: it can never accept anything.
|
|
249
|
+
*/
|
|
250
|
+
export function allowList(v, at) {
|
|
251
|
+
return isAbsent(v) ? [] : strList(v, at);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* A record with no prototype, holding only `init`'s own members. Every record the facade hands
|
|
255
|
+
* the backend is one, so a backend that reads a member by name or tests it with `in` sees only
|
|
256
|
+
* what the facade put there, never an inherited member of a polluted `Object.prototype`
|
|
257
|
+
* (0013 F04, security audit M1; ADR 0030 §3: an absent member is the core default).
|
|
258
|
+
*/
|
|
259
|
+
export function bare(init) {
|
|
260
|
+
return Object.assign(Object.create(null), init);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* A record argument: a plain object whose own enumerable members are all among `members`.
|
|
264
|
+
* Absent means no members. An unknown member is a TypeError, so a misspelling (`issuer` for
|
|
265
|
+
* `issuers`) can never silently drop a check. The message lists the accepted members, not the
|
|
266
|
+
* unknown one.
|
|
267
|
+
*
|
|
268
|
+
* Returns a {@link bare} copy of the caller's own enumerable members, each read exactly once, and
|
|
269
|
+
* the facade reads options only from that copy (0013 F04, review S2, security audit M1). An
|
|
270
|
+
* inherited member, such as `Object.prototype.ignoreAudience = true` set by a pollution bug
|
|
271
|
+
* elsewhere in the host app, is never read, so it can't loosen policy, lift a limit, stand in
|
|
272
|
+
* for a missing required member or inject a header: ADR 0030 §3 makes an absent option the core
|
|
273
|
+
* default, and the host's global state must not write one either. A non-enumerable own member is
|
|
274
|
+
* not read either, and reading once means a getter can't pass the type check with one value and
|
|
275
|
+
* reach the backend with another.
|
|
276
|
+
*/
|
|
277
|
+
export function record(v, at, members) {
|
|
278
|
+
const out = bare({});
|
|
279
|
+
if (isAbsent(v))
|
|
280
|
+
return out;
|
|
281
|
+
if (!isPlainObject(v))
|
|
282
|
+
throw fail(at, "a plain object");
|
|
283
|
+
for (const key of Object.keys(v)) {
|
|
284
|
+
if (!members.includes(key))
|
|
285
|
+
throw fail(at, `an object with only the members ${members.join(", ")}`);
|
|
286
|
+
out[key] = v[key];
|
|
287
|
+
}
|
|
288
|
+
return out;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Creates `out[key]` as an own, enumerable, writable, configurable data property holding `value`
|
|
292
|
+
* (ECMA-262 ValidateAndApplyPropertyDescriptor), the descriptor an object literal's member gets
|
|
293
|
+
* (CreateDataProperty). Never an assignment (0013 F13, security audit M1 follow-up): an assignment
|
|
294
|
+
* to a member `out` doesn't own consults `out`'s prototype (OrdinarySet), so on a result, which
|
|
295
|
+
* keeps `Object.prototype`, an accessor a pollution bug put on `Object.prototype.aad` would run
|
|
296
|
+
* its setter with the secret `aad` (ADR 0030 §7) and leave the result without it, and a
|
|
297
|
+
* non-writable inherited member would make the write a TypeError.
|
|
298
|
+
*
|
|
299
|
+
* The descriptor itself has no prototype: `Object.defineProperty` reads `value`, `get`, `set`,
|
|
300
|
+
* `writable`, ... through the descriptor's prototype chain (ToPropertyDescriptor), so with an
|
|
301
|
+
* ordinary descriptor object a polluted `Object.prototype.get` would make every write a TypeError.
|
|
302
|
+
*/
|
|
303
|
+
export function define(out, key, value) {
|
|
304
|
+
Object.defineProperty(out, key, bare({ value, enumerable: true, writable: true, configurable: true }));
|
|
305
|
+
}
|
|
306
|
+
/** {@link define}s `out[key]` only when `value` is present, so absent optional fields are left out. */
|
|
307
|
+
export function put(out, key, value) {
|
|
308
|
+
if (value !== undefined)
|
|
309
|
+
define(out, key, value);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Adds `value` at the end of `out` as an own data element ({@link define} at index `length`,
|
|
313
|
+
* which also grows `length`, ECMA-262 ArrayDefineOwnProperty). Never `push`: `push` assigns the
|
|
314
|
+
* next index (Array.prototype.push, Set(O, len, E, true)), so an accessor a pollution bug put on
|
|
315
|
+
* `Array.prototype[0]` or `Object.prototype[0]` would receive the element (a byte copy of a
|
|
316
|
+
* secret, a `JweRecipient` with its key) and leave a hole, and a non-writable one would make the
|
|
317
|
+
* call a TypeError (0013 F13).
|
|
318
|
+
*/
|
|
319
|
+
export function append(out, value) {
|
|
320
|
+
define(out, out.length, value);
|
|
321
|
+
}
|
|
322
|
+
// --- outputs (what the backend returned) -------------------------------------------------
|
|
323
|
+
function badResult(at, expected) {
|
|
324
|
+
return new TypeError(`polyjose: the backend returned ${at} that is not ${expected}`);
|
|
325
|
+
}
|
|
326
|
+
export function outStr(v, at) {
|
|
327
|
+
if (typeof v !== "string")
|
|
328
|
+
throw badResult(at, "a string");
|
|
329
|
+
return v;
|
|
330
|
+
}
|
|
331
|
+
export function outBytes(v, at) {
|
|
332
|
+
if (!isUint8Array(v))
|
|
333
|
+
throw badResult(at, "a Uint8Array");
|
|
334
|
+
return v;
|
|
335
|
+
}
|
|
336
|
+
export function outIndex(v, at) {
|
|
337
|
+
if (typeof v !== "number" || !Number.isInteger(v) || v < 0 || v > U32_MAX)
|
|
338
|
+
throw badResult(at, "an index");
|
|
339
|
+
return v;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* A list result (`errorCodes()`), item by item, copied: only the array's own elements below its
|
|
343
|
+
* `length` are read, so a hole reaches `item` as `undefined` (a TypeError naming its index) and is
|
|
344
|
+
* never read through a polluted `Array.prototype` (0013 F09).
|
|
345
|
+
*/
|
|
346
|
+
export function outList(v, at, item) {
|
|
347
|
+
if (!Array.isArray(v))
|
|
348
|
+
throw badResult(at, "an array");
|
|
349
|
+
const n = v.length;
|
|
350
|
+
const out = [];
|
|
351
|
+
for (let i = 0; i < n; i++)
|
|
352
|
+
append(out, item(Object.hasOwn(v, i) ? v[i] : undefined, `${at}[${i}]`));
|
|
353
|
+
return out;
|
|
354
|
+
}
|
|
355
|
+
/** JSON text the core serialized, parsed to an object (ADR 0030 §2: JWKs, headers, claims). */
|
|
356
|
+
export function outObject(v, at) {
|
|
357
|
+
let parsed;
|
|
358
|
+
try {
|
|
359
|
+
parsed = JSON.parse(outStr(v, at));
|
|
360
|
+
}
|
|
361
|
+
catch (e) {
|
|
362
|
+
if (e instanceof TypeError)
|
|
363
|
+
throw e;
|
|
364
|
+
throw badResult(at, "JSON text");
|
|
365
|
+
}
|
|
366
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
367
|
+
throw badResult(at, "a JSON object");
|
|
368
|
+
return parsed;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* One member of a backend result (a plain object, BACKEND.md), read only if the result owns it
|
|
372
|
+
* (0013 F09, security audit M1 follow-up). A member the backend left out is `undefined`, never
|
|
373
|
+
* a value a pollution bug put on `Object.prototype`: an absent optional field (`kid`,
|
|
374
|
+
* `keyIndex`, a header, `aad`) stays absent, and a missing required one is a TypeError.
|
|
375
|
+
*/
|
|
376
|
+
export function member(v, name, at) {
|
|
377
|
+
if (typeof v !== "object" || v === null)
|
|
378
|
+
throw badResult(at, "an object");
|
|
379
|
+
return Object.hasOwn(v, name) ? v[name] : undefined;
|
|
380
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The public records of the JS facade: ADR 0030 §3–§4 with camelCase fields (§2).
|
|
3
|
+
*
|
|
4
|
+
* - Allow-lists (`allowedAlgs`, `allowedEnc`) are required, with no default (ADR 0007,
|
|
5
|
+
* RFC 8725 §3.1); omitting one does not type-check. Plain JS callers that omit one get
|
|
6
|
+
* `AlgorithmNotAllowed` from the core, never a default list.
|
|
7
|
+
* - Every tunable is optional, and absent means the core default (ADR 0030 §3). The facade
|
|
8
|
+
* writes no default of its own.
|
|
9
|
+
* - Algorithm names are the IANA JOSE registry strings, passed as given (ADR 0030 D1).
|
|
10
|
+
* - JSON values (JWKs, JWK Sets, headers, claims, JWS/JWE JSON serializations) are accepted
|
|
11
|
+
* as JSON text or as a plain object (ADR 0030 §2); results come back as parsed objects
|
|
12
|
+
* (JWKs, headers, claims) or strings (tokens and JSON serializations).
|
|
13
|
+
*/
|
|
14
|
+
/** A JSON value as `JSON.parse` returns it. */
|
|
15
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | JsonObject;
|
|
16
|
+
/** A JSON object as `JSON.parse` returns it. */
|
|
17
|
+
export interface JsonObject {
|
|
18
|
+
[member: string]: JsonValue;
|
|
19
|
+
}
|
|
20
|
+
/** A JWK (RFC 7517 §4) as an object. */
|
|
21
|
+
export interface JwkObject {
|
|
22
|
+
kty: string;
|
|
23
|
+
[member: string]: JsonValue;
|
|
24
|
+
}
|
|
25
|
+
/** A JWK Set (RFC 7517 §5) as an object. */
|
|
26
|
+
export interface JwkSetObject {
|
|
27
|
+
keys: JsonValue[];
|
|
28
|
+
[member: string]: JsonValue;
|
|
29
|
+
}
|
|
30
|
+
/** A JWK: its JSON text or a plain object (ADR 0030 §2, C1). */
|
|
31
|
+
export type Jwk = string | JwkObject;
|
|
32
|
+
/** A JWK Set: its JSON text or a plain object. */
|
|
33
|
+
export type JwkSet = string | JwkSetObject;
|
|
34
|
+
/** A JSON object argument (a header, a Claims Set, a JWS/JWE JSON serialization): text or a plain object. */
|
|
35
|
+
export type JsonInput = string | JsonObject;
|
|
36
|
+
/**
|
|
37
|
+
* JWK Thumbprint hash names (RFC 7638 §3.4, RFC 9278 §4): the nine names the core accepts,
|
|
38
|
+
* matched exactly (ADR 0054 §8.2). Each hash has its IANA Named Information name (`sha-256`,
|
|
39
|
+
* canonical, the one the URI carries), the ADR 0030 §4 form (`SHA-256`) and the panva/jose form
|
|
40
|
+
* (`sha256`). SHA-1 is not offered (ADR 0022). The core parses the name; the facade passes it
|
|
41
|
+
* as given.
|
|
42
|
+
*/
|
|
43
|
+
export type ThumbprintHash = "sha-256" | "SHA-256" | "sha256" | "sha-384" | "SHA-384" | "sha384" | "sha-512" | "SHA-512" | "sha512";
|
|
44
|
+
/** EC curves `jwkGenerateEc` takes (RFC 7518 §6.2.1.1, RFC 8812 §3.1). */
|
|
45
|
+
export type EcCurve = "P-256" | "P-384" | "P-521" | "secp256k1";
|
|
46
|
+
/** OKP curves `jwkGenerateOkp` takes (RFC 8037 §2). */
|
|
47
|
+
export type OkpCurve = "Ed25519" | "X25519";
|
|
48
|
+
/** Key encodings (ADR 0018 §3): PKCS#8 (RFC 5958), SEC1 (RFC 5915), SPKI (RFC 5280), PKCS#1 (RFC 8017). */
|
|
49
|
+
export type KeyFormat = "pkcs8" | "sec1" | "spki" | "pkcs1";
|
|
50
|
+
/** The JWE JSON serializations (RFC 7516 §7.2.1 general, §7.2.2 flattened). */
|
|
51
|
+
export type JweJsonForm = "general" | "flattened";
|
|
52
|
+
/**
|
|
53
|
+
* `jwksSelect` criteria (RFC 7517 §4.2, §4.4, §4.5; ADR 0054 §1). Each is optional and passed
|
|
54
|
+
* to the core as given: `alg` a JWS or JWE key management algorithm, `keyUse` `sig` or `enc`.
|
|
55
|
+
*/
|
|
56
|
+
export interface JwkSelector {
|
|
57
|
+
kid?: string | undefined;
|
|
58
|
+
alg?: string | undefined;
|
|
59
|
+
keyUse?: string | undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* `jwsSignCompact` options (RFC 7515 §7.1). Exactly one of `protectedHeader` and
|
|
63
|
+
* `protectedB64u`; both or neither fail with `InvalidHeader` in the core. `protectedB64u`
|
|
64
|
+
* signs over the caller's exact octets (RFC 7515 §5.1 step 3).
|
|
65
|
+
*/
|
|
66
|
+
export interface JwsSignOptions {
|
|
67
|
+
protectedHeader?: JsonInput | undefined;
|
|
68
|
+
protectedB64u?: string | undefined;
|
|
69
|
+
/** Leave the payload out of the output (RFC 7515 Appendix F). */
|
|
70
|
+
detached?: boolean | undefined;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* One signer of a JWS JSON serialization (RFC 7515 §7.2). A signer may carry only an
|
|
74
|
+
* unprotected header (RFC 7515 §7.2.1, ADR 0045 §1). This record holds a key: don't log it.
|
|
75
|
+
*/
|
|
76
|
+
export interface JwsSigner {
|
|
77
|
+
key: Jwk;
|
|
78
|
+
protectedHeader?: JsonInput | undefined;
|
|
79
|
+
protectedB64u?: string | undefined;
|
|
80
|
+
unprotectedHeader?: JsonInput | undefined;
|
|
81
|
+
}
|
|
82
|
+
/** JWS verification options (RFC 7515 §5.2, RFC 8725 §3.1; ADR 0026 §7 limits). */
|
|
83
|
+
export interface JwsVerifyOptions {
|
|
84
|
+
/** The accepted `alg` names. Required; `none` is never accepted (ADR 0007). */
|
|
85
|
+
allowedAlgs: readonly string[];
|
|
86
|
+
/** The detached payload (RFC 7515 Appendix F). */
|
|
87
|
+
detachedPayload?: Uint8Array | undefined;
|
|
88
|
+
maxInputLen?: number | undefined;
|
|
89
|
+
maxSignatures?: number | undefined;
|
|
90
|
+
}
|
|
91
|
+
/** A verified JWS. `unprotectedHeader` is not integrity-protected (ADR 0049 §2). */
|
|
92
|
+
export interface JwsVerified {
|
|
93
|
+
alg: string;
|
|
94
|
+
payload: Uint8Array;
|
|
95
|
+
protectedHeader?: JsonObject;
|
|
96
|
+
unprotectedHeader?: JsonObject;
|
|
97
|
+
/** The index of the signature that verified (0 for compact and flattened; ADR 0026 §3). */
|
|
98
|
+
signatureIndex: number;
|
|
99
|
+
}
|
|
100
|
+
/** One JWE recipient (RFC 7516 §5.1, RFC 7518 §4). This record holds a key: don't log it. */
|
|
101
|
+
export interface JweRecipient {
|
|
102
|
+
alg: string;
|
|
103
|
+
key: Jwk;
|
|
104
|
+
header?: JsonInput | undefined;
|
|
105
|
+
apu?: Uint8Array | undefined;
|
|
106
|
+
apv?: Uint8Array | undefined;
|
|
107
|
+
p2c?: number | undefined;
|
|
108
|
+
p2sLen?: number | undefined;
|
|
109
|
+
}
|
|
110
|
+
/** JWE encryption options (RFC 7516 §5.1). */
|
|
111
|
+
export interface JweEncryptOptions {
|
|
112
|
+
enc: string;
|
|
113
|
+
recipients: readonly JweRecipient[];
|
|
114
|
+
protectedHeader?: JsonInput | undefined;
|
|
115
|
+
sharedUnprotectedHeader?: JsonInput | undefined;
|
|
116
|
+
aad?: Uint8Array | undefined;
|
|
117
|
+
/** `zip: "DEF"` (RFC 7516 §4.1.3); `UnsupportedAlgorithm` while bindings build without `deflate`. */
|
|
118
|
+
compress?: boolean | undefined;
|
|
119
|
+
}
|
|
120
|
+
/** JWE decryption options (RFC 7516 §5.2, RFC 8725 §3.1; ADR 0023, 0017 bounds). */
|
|
121
|
+
export interface JweDecryptOptions {
|
|
122
|
+
/** The accepted key management `alg` names. Required (ADR 0007). */
|
|
123
|
+
allowedAlgs: readonly string[];
|
|
124
|
+
/** The accepted content encryption `enc` names. Required (ADR 0007). */
|
|
125
|
+
allowedEnc: readonly string[];
|
|
126
|
+
maxPbes2Count?: number | undefined;
|
|
127
|
+
maxDecompressedLen?: number | undefined;
|
|
128
|
+
maxInputLen?: number | undefined;
|
|
129
|
+
maxRecipients?: number | undefined;
|
|
130
|
+
/** The `crit` extensions the caller understands (RFC 7516 §4.1.13). */
|
|
131
|
+
understoodCrit?: readonly string[] | undefined;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A decrypted JWE. Only `protectedHeader` is integrity-protected; `header`,
|
|
135
|
+
* `sharedUnprotectedHeader` and `recipientHeader` may hold unprotected members (ADR 0050 §3).
|
|
136
|
+
*/
|
|
137
|
+
export interface JweDecrypted {
|
|
138
|
+
plaintext: Uint8Array;
|
|
139
|
+
/** The union of the headers used (RFC 7516 §5.2 step 5). */
|
|
140
|
+
header: JsonObject;
|
|
141
|
+
protectedHeader?: JsonObject;
|
|
142
|
+
sharedUnprotectedHeader?: JsonObject;
|
|
143
|
+
recipientHeader?: JsonObject;
|
|
144
|
+
aad?: Uint8Array;
|
|
145
|
+
recipientIndex: number;
|
|
146
|
+
}
|
|
147
|
+
/** JWT signing options (RFC 7519 §7.1; ADR 0027 §8). */
|
|
148
|
+
export interface JwtSignOptions {
|
|
149
|
+
alg: string;
|
|
150
|
+
/** The `typ` header; absent gives the core default `JWT`. */
|
|
151
|
+
typ?: string | undefined;
|
|
152
|
+
/** Omit `typ` (RFC 7519 §5.1). */
|
|
153
|
+
omitTyp?: boolean | undefined;
|
|
154
|
+
/** An RFC 9068 access token (`typ: at+jwt`). */
|
|
155
|
+
accessToken?: boolean | undefined;
|
|
156
|
+
/** Extra protected header parameters. */
|
|
157
|
+
header?: JsonInput | undefined;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* JWT validation options (RFC 7519 §7.2, RFC 8725 §3; ADR 0027 policy, ADR 0029 key sets).
|
|
161
|
+
* Each field maps to one core `Policy` method; absent or `false` keeps the core default.
|
|
162
|
+
*/
|
|
163
|
+
export interface JwtVerifyOptions {
|
|
164
|
+
/** The accepted JWS `alg` names. Required (ADR 0007). */
|
|
165
|
+
allowedAlgs: readonly string[];
|
|
166
|
+
/**
|
|
167
|
+
* The current time in whole seconds since the epoch (RFC 7519 §2). When absent the facade
|
|
168
|
+
* passes `Math.floor(Date.now() / 1000)` (ADR 0028 §4, ADR 0030 §4).
|
|
169
|
+
*/
|
|
170
|
+
now?: number | undefined;
|
|
171
|
+
issuers?: readonly string[] | undefined;
|
|
172
|
+
audiences?: readonly string[] | undefined;
|
|
173
|
+
subject?: string | undefined;
|
|
174
|
+
/** Clock skew in seconds (core default 60, at most 600). */
|
|
175
|
+
leeway?: number | undefined;
|
|
176
|
+
maxAge?: number | undefined;
|
|
177
|
+
requiredClaims?: readonly string[] | undefined;
|
|
178
|
+
allowMissingExp?: boolean | undefined;
|
|
179
|
+
ignoreAudience?: boolean | undefined;
|
|
180
|
+
typ?: string | undefined;
|
|
181
|
+
anyTyp?: boolean | undefined;
|
|
182
|
+
/**
|
|
183
|
+
* `"access_token"`: the RFC 9068 §4 JWT access-token profile (`Policy::access_token`), the only
|
|
184
|
+
* profile, so a typo fails to compile (0013 F04, review N5). The type is the only check: an
|
|
185
|
+
* untyped caller's other string still reaches the core, which refuses it with
|
|
186
|
+
* `UnsupportedAlgorithm` (ADR 0030 §4).
|
|
187
|
+
*/
|
|
188
|
+
profile?: "access_token" | undefined;
|
|
189
|
+
maxKeyCandidates?: number | undefined;
|
|
190
|
+
maxInputLen?: number | undefined;
|
|
191
|
+
}
|
|
192
|
+
/** A validated JWT. */
|
|
193
|
+
export interface JwtVerified {
|
|
194
|
+
claims: JsonObject;
|
|
195
|
+
header: JsonObject;
|
|
196
|
+
alg: string;
|
|
197
|
+
/** The JWK Set position of the key that verified; present only for a key set (ADR 0029 §4). */
|
|
198
|
+
keyIndex?: number;
|
|
199
|
+
kid?: string;
|
|
200
|
+
}
|
|
201
|
+
/** A validated nested JWT (ADR 0027 §7). */
|
|
202
|
+
export interface JwtNestedVerified {
|
|
203
|
+
inner: JwtVerified;
|
|
204
|
+
outerHeader: JsonObject;
|
|
205
|
+
recipientIndex: number;
|
|
206
|
+
}
|
package/facade/types.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The public records of the JS facade: ADR 0030 §3–§4 with camelCase fields (§2).
|
|
3
|
+
*
|
|
4
|
+
* - Allow-lists (`allowedAlgs`, `allowedEnc`) are required, with no default (ADR 0007,
|
|
5
|
+
* RFC 8725 §3.1); omitting one does not type-check. Plain JS callers that omit one get
|
|
6
|
+
* `AlgorithmNotAllowed` from the core, never a default list.
|
|
7
|
+
* - Every tunable is optional, and absent means the core default (ADR 0030 §3). The facade
|
|
8
|
+
* writes no default of its own.
|
|
9
|
+
* - Algorithm names are the IANA JOSE registry strings, passed as given (ADR 0030 D1).
|
|
10
|
+
* - JSON values (JWKs, JWK Sets, headers, claims, JWS/JWE JSON serializations) are accepted
|
|
11
|
+
* as JSON text or as a plain object (ADR 0030 §2); results come back as parsed objects
|
|
12
|
+
* (JWKs, headers, claims) or strings (tokens and JSON serializations).
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@polyjose/node`: the shared facade (`bindings/js`, ADR 0031 §1, ADR 0047) bound once to the
|
|
3
|
+
* napi-rs backend (ADR 0031 §2), re-exported as the package's API (BACKEND.md, "A package entry").
|
|
4
|
+
*
|
|
5
|
+
* Every operation of ADR 0030 §4 returns a Promise and runs on the libuv thread pool; a core
|
|
6
|
+
* failure rejects with a `PolyjoseError` whose `code` is one of `ERROR_CODES` (ADR 0030 §5).
|
|
7
|
+
* ESM only; CommonJS callers load it with `require()` (require(esm), Node >= 22.12, ADR 0031 §3).
|
|
8
|
+
*/
|
|
9
|
+
export declare const version: () => Promise<string>, errorCodes: () => Promise<string[]>, base64urlEncode: (bytes: Uint8Array) => Promise<string>, base64urlDecode: (text: string) => Promise<Uint8Array>, jwkParse: (jwk: import("./facade/types.js").Jwk) => Promise<import("./facade/types.js").JwkObject>, jwkToPublic: (jwk: import("./facade/types.js").Jwk) => Promise<import("./facade/types.js").JwkObject>, jwkThumbprint: (jwk: import("./facade/types.js").Jwk, hash?: import("./facade/types.js").ThumbprintHash) => Promise<string>, jwkThumbprintUri: (jwk: import("./facade/types.js").Jwk, hash?: import("./facade/types.js").ThumbprintHash) => Promise<string>, jwksSelect: (jwks: import("./facade/types.js").JwkSet, selector?: import("./facade/types.js").JwkSelector) => Promise<import("./facade/types.js").JwkObject>, jwkGenerateOct: (lenBytes: number) => Promise<import("./facade/types.js").JwkObject>, jwkGenerateEc: (crv: import("./facade/types.js").EcCurve) => Promise<import("./facade/types.js").JwkObject>, jwkGenerateOkp: (crv: import("./facade/types.js").OkpCurve) => Promise<import("./facade/types.js").JwkObject>, jwkGenerateForAlg: (alg: string) => Promise<import("./facade/types.js").JwkObject>, jwkFromPem: (pem: string) => Promise<import("./facade/types.js").JwkObject>, jwkFromDer: (der: Uint8Array, format: import("./facade/types.js").KeyFormat) => Promise<import("./facade/types.js").JwkObject>, jwkToPem: (jwk: import("./facade/types.js").Jwk, format: import("./facade/types.js").KeyFormat) => Promise<string>, jwkToDer: (jwk: import("./facade/types.js").Jwk, format: import("./facade/types.js").KeyFormat) => Promise<Uint8Array>, jwsSignCompact: (payload: Uint8Array, key: import("./facade/types.js").Jwk, options: import("./facade/types.js").JwsSignOptions) => Promise<string>, jwsSignFlattened: (payload: Uint8Array, signer: import("./facade/types.js").JwsSigner, detached?: boolean) => Promise<string>, jwsSignGeneral: (payload: Uint8Array, signers: readonly import("./facade/types.js").JwsSigner[], detached?: boolean) => Promise<string>, jwsVerifyCompact: (token: string, key: import("./facade/types.js").Jwk, options: import("./facade/types.js").JwsVerifyOptions) => Promise<import("./facade/types.js").JwsVerified>, jwsVerifyJson: (jws: import("./facade/types.js").JsonInput, key: import("./facade/types.js").Jwk, options: import("./facade/types.js").JwsVerifyOptions) => Promise<import("./facade/types.js").JwsVerified>, jweEncryptCompact: (plaintext: Uint8Array, options: import("./facade/types.js").JweEncryptOptions) => Promise<string>, jweEncryptJson: (plaintext: Uint8Array, options: import("./facade/types.js").JweEncryptOptions, form: import("./facade/types.js").JweJsonForm) => Promise<string>, jweDecryptCompact: (token: string, key: import("./facade/types.js").Jwk, options: import("./facade/types.js").JweDecryptOptions) => Promise<import("./facade/types.js").JweDecrypted>, jweDecryptJson: (jwe: import("./facade/types.js").JsonInput, key: import("./facade/types.js").Jwk, options: import("./facade/types.js").JweDecryptOptions) => Promise<import("./facade/types.js").JweDecrypted>, jwtSign: (claims: import("./facade/types.js").JsonInput, key: import("./facade/types.js").Jwk, options: import("./facade/types.js").JwtSignOptions) => Promise<string>, jwtVerify: (token: string, key: import("./facade/types.js").Jwk, options: import("./facade/types.js").JwtVerifyOptions) => Promise<import("./facade/types.js").JwtVerified>, jwtVerifyWithJwks: (token: string, jwks: import("./facade/types.js").JwkSet, options: import("./facade/types.js").JwtVerifyOptions) => Promise<import("./facade/types.js").JwtVerified>, jwtEncryptNested: (signedJwt: string, options: import("./facade/types.js").JweEncryptOptions) => Promise<string>, jwtDecryptNested: (token: string, decryptKey: import("./facade/types.js").Jwk, decryptOptions: import("./facade/types.js").JweDecryptOptions, key: import("./facade/types.js").Jwk, verifyOptions: import("./facade/types.js").JwtVerifyOptions) => Promise<import("./facade/types.js").JwtNestedVerified>, jwtDecryptNestedWithJwks: (token: string, decryptKey: import("./facade/types.js").Jwk, decryptOptions: import("./facade/types.js").JweDecryptOptions, jwks: import("./facade/types.js").JwkSet, verifyOptions: import("./facade/types.js").JwtVerifyOptions) => Promise<import("./facade/types.js").JwtNestedVerified>, PolyjoseError: typeof import("./facade/api.js").PolyjoseError;
|
|
10
|
+
export { ERROR_CODES } from "./facade/api.js";
|
|
11
|
+
export type * from "./facade/api.js";
|
package/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@polyjose/node`: the shared facade (`bindings/js`, ADR 0031 §1, ADR 0047) bound once to the
|
|
3
|
+
* napi-rs backend (ADR 0031 §2), re-exported as the package's API (BACKEND.md, "A package entry").
|
|
4
|
+
*
|
|
5
|
+
* Every operation of ADR 0030 §4 returns a Promise and runs on the libuv thread pool; a core
|
|
6
|
+
* failure rejects with a `PolyjoseError` whose `code` is one of `ERROR_CODES` (ADR 0030 §5).
|
|
7
|
+
* ESM only; CommonJS callers load it with `require()` (require(esm), Node >= 22.12, ADR 0031 §3).
|
|
8
|
+
*/
|
|
9
|
+
import { createApi } from "./facade/api.js";
|
|
10
|
+
import { backend } from "./backend.js";
|
|
11
|
+
export const { version, errorCodes, base64urlEncode, base64urlDecode, jwkParse, jwkToPublic, jwkThumbprint, jwkThumbprintUri, jwksSelect, jwkGenerateOct, jwkGenerateEc, jwkGenerateOkp, jwkGenerateForAlg, jwkFromPem, jwkFromDer, jwkToPem, jwkToDer, jwsSignCompact, jwsSignFlattened, jwsSignGeneral, jwsVerifyCompact, jwsVerifyJson, jweEncryptCompact, jweEncryptJson, jweDecryptCompact, jweDecryptJson, jwtSign, jwtVerify, jwtVerifyWithJwks, jwtEncryptNested, jwtDecryptNested, jwtDecryptNestedWithJwks, PolyjoseError, } = createApi(backend);
|
|
12
|
+
export { ERROR_CODES } from "./facade/api.js";
|