@smonn/ids 0.9.0 → 0.9.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/README.md +60 -930
- package/dist/cli.mjs +398 -513
- package/dist/cli.mjs.map +1 -1
- package/dist/{codec-shell-C7_B4oum.mjs → codec-shell-CW2sD6BU.mjs} +3 -3
- package/dist/{codec-shell-C7_B4oum.mjs.map → codec-shell-CW2sD6BU.mjs.map} +1 -1
- package/dist/drizzle.d.mts +32 -2
- package/dist/drizzle.d.mts.map +1 -0
- package/dist/index.mjs +1 -1
- package/dist/{key-material-DUHhmMq-.mjs → key-material-gOnqTNoV.mjs} +3 -3
- package/dist/{key-material-DUHhmMq-.mjs.map → key-material-gOnqTNoV.mjs.map} +1 -1
- package/dist/kysely.d.mts.map +1 -1
- package/dist/kysely.mjs +2 -3
- package/dist/kysely.mjs.map +1 -1
- package/dist/{opaque-BQOlZ2oD.mjs → opaque-BpqxV8oB.mjs} +8 -8
- package/dist/{opaque-BQOlZ2oD.mjs.map → opaque-BpqxV8oB.mjs.map} +1 -1
- package/dist/opaque.mjs +1 -1
- package/dist/prisma.d.mts +1 -2
- package/dist/prisma.d.mts.map +1 -1
- package/dist/prisma.mjs +1 -2
- package/dist/prisma.mjs.map +1 -1
- package/dist/{reverse-C12D1btB.mjs → reverse-d5uEoIET.mjs} +4 -4
- package/dist/{reverse-C12D1btB.mjs.map → reverse-d5uEoIET.mjs.map} +1 -1
- package/dist/reverse.mjs +1 -1
- package/dist/{signed-CwqKTFaQ.mjs → signed-BnRSC03a.mjs} +13 -13
- package/dist/signed-BnRSC03a.mjs.map +1 -0
- package/dist/signed.d.mts.map +1 -1
- package/dist/signed.mjs +1 -1
- package/dist/{timestamp-BjIMQkJf.mjs → timestamp-BbZL8hwg.mjs} +5 -5
- package/dist/{timestamp-BjIMQkJf.mjs.map → timestamp-BbZL8hwg.mjs.map} +1 -1
- package/dist/{timestamp-bytes-Bbg6Y66Z.mjs → timestamp-bytes-DoFjLjDp.mjs} +3 -2
- package/dist/timestamp-bytes-DoFjLjDp.mjs.map +1 -0
- package/dist/{wrapped-DKOsN_dq.mjs → wrapped-BI9UXnAm.mjs} +21 -16
- package/dist/wrapped-BI9UXnAm.mjs.map +1 -0
- package/dist/wrapped.d.mts.map +1 -1
- package/dist/wrapped.mjs +1 -1
- package/package.json +5 -5
- package/dist/drizzle-CHtyDXpv.d.mts +0 -33
- package/dist/drizzle-CHtyDXpv.d.mts.map +0 -1
- package/dist/signed-CwqKTFaQ.mjs.map +0 -1
- package/dist/timestamp-bytes-Bbg6Y66Z.mjs.map +0 -1
- package/dist/wrapped-DKOsN_dq.mjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,10 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as isIdsError } from "./error-Cp5qYZcv.mjs";
|
|
3
|
-
import { t as createTimestampId } from "./timestamp-
|
|
4
|
-
import { i as importOpaqueKey, n as decodeOpaqueKey, r as encodeOpaqueKey, t as createOpaqueTimestampId } from "./opaque-
|
|
5
|
-
import { t as createReverseTimestampId } from "./reverse-
|
|
6
|
-
import { i as importSigningKey, n as decodeSigningKey, r as encodeSigningKey, t as createSignedTimestampId } from "./signed-
|
|
7
|
-
import { i as importWrappingKey, n as decodeWrappingKey, r as encodeWrappingKey, t as createWrappedKeyId } from "./wrapped-
|
|
3
|
+
import { t as createTimestampId } from "./timestamp-BbZL8hwg.mjs";
|
|
4
|
+
import { i as importOpaqueKey, n as decodeOpaqueKey, r as encodeOpaqueKey, t as createOpaqueTimestampId } from "./opaque-BpqxV8oB.mjs";
|
|
5
|
+
import { t as createReverseTimestampId } from "./reverse-d5uEoIET.mjs";
|
|
6
|
+
import { i as importSigningKey, n as decodeSigningKey, r as encodeSigningKey, t as createSignedTimestampId } from "./signed-BnRSC03a.mjs";
|
|
7
|
+
import { i as importWrappingKey, n as decodeWrappingKey, r as encodeWrappingKey, t as createWrappedKeyId } from "./wrapped-BI9UXnAm.mjs";
|
|
8
|
+
//#region src/cli/key-io.ts
|
|
9
|
+
function isKeyFormatError(result) {
|
|
10
|
+
return result !== "hex" && result !== "base64url";
|
|
11
|
+
}
|
|
12
|
+
function parseKeyFormatFlag(values) {
|
|
13
|
+
const fromFlag = values.get("--key-format");
|
|
14
|
+
if (fromFlag === void 0) return void 0;
|
|
15
|
+
if (fromFlag === "") return "--key-format requires a value";
|
|
16
|
+
if (fromFlag === "hex" || fromFlag === "base64url") return fromFlag;
|
|
17
|
+
return `--key-format must be hex or base64url, got '${fromFlag}'`;
|
|
18
|
+
}
|
|
19
|
+
function parseKeyFormatFromFlag(values) {
|
|
20
|
+
const fromFlag = parseKeyFormatFlag(values);
|
|
21
|
+
if (fromFlag === void 0) return "hex";
|
|
22
|
+
return fromFlag;
|
|
23
|
+
}
|
|
24
|
+
function parseKeyFormat(values, opts, facet) {
|
|
25
|
+
const fromFlag = parseKeyFormatFlag(values);
|
|
26
|
+
if (fromFlag !== void 0) return fromFlag;
|
|
27
|
+
const fromEnv = (opts.env ?? process.env)[facet.formatEnvVar];
|
|
28
|
+
if (fromEnv === void 0 || fromEnv === "") return "hex";
|
|
29
|
+
if (fromEnv === "hex" || fromEnv === "base64url") return fromEnv;
|
|
30
|
+
return `${facet.formatEnvVar} must be hex or base64url, got '${fromEnv}'`;
|
|
31
|
+
}
|
|
32
|
+
async function loadKey(opts, format, facet) {
|
|
33
|
+
const raw = (opts.env ?? process.env)[facet.envVar];
|
|
34
|
+
if (raw === void 0 || raw === "") return `missing ${facet.envVar} environment variable`;
|
|
35
|
+
try {
|
|
36
|
+
return await facet.import(facet.decode(raw, format));
|
|
37
|
+
} catch (err) {
|
|
38
|
+
return err.message;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
8
42
|
//#region src/cli/codec-options.ts
|
|
9
43
|
function codecOpts(opts) {
|
|
10
44
|
const o = { allowDuplicateBrand: true };
|
|
@@ -13,73 +47,6 @@ function codecOpts(opts) {
|
|
|
13
47
|
return o;
|
|
14
48
|
}
|
|
15
49
|
//#endregion
|
|
16
|
-
//#region src/cli/format.ts
|
|
17
|
-
function formatCliError(err) {
|
|
18
|
-
return isIdsError(err) ? `${err.code}: ${err.message}` : err instanceof Error ? err.message : String(err);
|
|
19
|
-
}
|
|
20
|
-
function formatWrappedInspectOutput(result) {
|
|
21
|
-
const inputLine = describeInputForm(result.input, result.canonical);
|
|
22
|
-
return [
|
|
23
|
-
`brand: ${result.brand}`,
|
|
24
|
-
`lookup-key: ${result.lookupKey.toString()}`,
|
|
25
|
-
`canonical: ${result.canonical}`,
|
|
26
|
-
`input: ${inputLine}`,
|
|
27
|
-
""
|
|
28
|
-
].join("\n");
|
|
29
|
-
}
|
|
30
|
-
function formatSignedInspectOutput(result) {
|
|
31
|
-
const relative = formatRelative(result.timestamp.getTime(), result.nowMs);
|
|
32
|
-
const inputLine = describeInputForm(result.input, result.canonical);
|
|
33
|
-
const lines = [`brand: ${result.brand}`, `timestamp: ${result.timestamp.toISOString()} (${relative})`];
|
|
34
|
-
if (result.verification !== void 0) lines.push(`verification: ${result.verification}`);
|
|
35
|
-
lines.push(`canonical: ${result.canonical}`, `input: ${inputLine}`, "");
|
|
36
|
-
return lines.join("\n");
|
|
37
|
-
}
|
|
38
|
-
function formatInspectOutput(result) {
|
|
39
|
-
const relative = formatRelative(result.timestamp.getTime(), result.nowMs);
|
|
40
|
-
const inputLine = describeInputForm(result.input, result.canonical);
|
|
41
|
-
return [
|
|
42
|
-
`brand: ${result.brand}`,
|
|
43
|
-
`timestamp: ${result.timestamp.toISOString()} (${relative})`,
|
|
44
|
-
`canonical: ${result.canonical}`,
|
|
45
|
-
`input: ${inputLine}`,
|
|
46
|
-
""
|
|
47
|
-
].join("\n");
|
|
48
|
-
}
|
|
49
|
-
function describeInputForm(input, canonical) {
|
|
50
|
-
if (input === canonical) return "canonical";
|
|
51
|
-
const notes = [];
|
|
52
|
-
if (input !== input.toLowerCase()) notes.push("was uppercase");
|
|
53
|
-
if (/[ilo]/i.test(input.slice(4))) notes.push("used Crockford aliases");
|
|
54
|
-
return `not canonical (${notes.join(" + ")})`;
|
|
55
|
-
}
|
|
56
|
-
const msPerMinute = 60 * 1e3;
|
|
57
|
-
const msPerHour = 60 * msPerMinute;
|
|
58
|
-
const msPerDay = 24 * msPerHour;
|
|
59
|
-
const daysPerMonth = 30.44;
|
|
60
|
-
const monthsPerYear = 12;
|
|
61
|
-
function formatRelative(thenMs, nowMs) {
|
|
62
|
-
const diff = nowMs - thenMs;
|
|
63
|
-
const abs = Math.abs(diff);
|
|
64
|
-
const suffix = diff < 0 ? "from now" : "ago";
|
|
65
|
-
const head = headUnits(abs);
|
|
66
|
-
return head === "" ? "just now" : `${head} ${suffix}`;
|
|
67
|
-
}
|
|
68
|
-
function headUnits(abs) {
|
|
69
|
-
if (abs < msPerMinute) return "";
|
|
70
|
-
if (abs < msPerHour) return unit(Math.round(abs / msPerMinute), "minute");
|
|
71
|
-
if (abs < msPerDay) return unit(Math.round(abs / msPerHour), "hour");
|
|
72
|
-
if (abs < msPerDay * daysPerMonth) return unit(Math.round(abs / msPerDay), "day");
|
|
73
|
-
const totalMonths = Math.round(abs / (msPerDay * daysPerMonth));
|
|
74
|
-
if (totalMonths < monthsPerYear) return unit(totalMonths, "month");
|
|
75
|
-
const years = Math.floor(totalMonths / monthsPerYear);
|
|
76
|
-
const months = totalMonths % monthsPerYear;
|
|
77
|
-
return months === 0 ? unit(years, "year") : `${unit(years, "year")} ${unit(months, "month")}`;
|
|
78
|
-
}
|
|
79
|
-
function unit(n, name) {
|
|
80
|
-
return `${n} ${n === 1 ? name : `${name}s`}`;
|
|
81
|
-
}
|
|
82
|
-
//#endregion
|
|
83
50
|
//#region src/cli/constants.ts
|
|
84
51
|
const maxGenerateCount = 1e4;
|
|
85
52
|
//#endregion
|
|
@@ -95,19 +62,12 @@ function splitFlagToken(arg) {
|
|
|
95
62
|
inlineValue: arg.slice(eq + 1)
|
|
96
63
|
};
|
|
97
64
|
}
|
|
98
|
-
function splitFlags(args) {
|
|
65
|
+
function splitFlags(args, valueFlags) {
|
|
99
66
|
const flags = /* @__PURE__ */ new Set();
|
|
100
67
|
const values = /* @__PURE__ */ new Map();
|
|
101
68
|
const positionals = [];
|
|
102
69
|
const errors = [];
|
|
103
70
|
const seenFlags = /* @__PURE__ */ new Set();
|
|
104
|
-
const valueFlags = new Set([
|
|
105
|
-
"--count",
|
|
106
|
-
"-c",
|
|
107
|
-
"--bits",
|
|
108
|
-
"--key-format",
|
|
109
|
-
"--kind"
|
|
110
|
-
]);
|
|
111
71
|
const addFlag = (flag) => {
|
|
112
72
|
const canonical = canonicalFlag(flag);
|
|
113
73
|
if (seenFlags.has(canonical)) errors.push(`duplicate flag: ${canonical}`);
|
|
@@ -117,11 +77,6 @@ function splitFlags(args) {
|
|
|
117
77
|
for (let i = 0; i < args.length; i++) {
|
|
118
78
|
const raw = args[i];
|
|
119
79
|
const { flag, inlineValue } = splitFlagToken(raw);
|
|
120
|
-
if (flag === "--opaque" || flag === "--wrapped" || flag === "--reverse" || flag === "--signed") {
|
|
121
|
-
addFlag(flag);
|
|
122
|
-
if (inlineValue !== void 0) errors.push(`flag does not take a value: ${flag}`);
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
80
|
if (valueFlags.has(flag)) {
|
|
126
81
|
if (inlineValue !== void 0) {
|
|
127
82
|
addFlag(flag);
|
|
@@ -141,6 +96,7 @@ function splitFlags(args) {
|
|
|
141
96
|
}
|
|
142
97
|
if (flag.startsWith("-")) {
|
|
143
98
|
addFlag(flag);
|
|
99
|
+
if (inlineValue !== void 0) errors.push(`flag does not take a value: ${flag}`);
|
|
144
100
|
continue;
|
|
145
101
|
}
|
|
146
102
|
positionals.push(raw);
|
|
@@ -156,7 +112,7 @@ function canonicalFlag(flag) {
|
|
|
156
112
|
if (flag === "-c") return "--count";
|
|
157
113
|
return flag;
|
|
158
114
|
}
|
|
159
|
-
const knownFlags = new Set([
|
|
115
|
+
const knownFlags = /* @__PURE__ */ new Set([
|
|
160
116
|
"--opaque",
|
|
161
117
|
"--wrapped",
|
|
162
118
|
"--reverse",
|
|
@@ -199,211 +155,267 @@ function isKindError(result) {
|
|
|
199
155
|
return result !== "u32" && result !== "i32" && result !== "u64" && result !== "i64";
|
|
200
156
|
}
|
|
201
157
|
//#endregion
|
|
202
|
-
//#region src/cli/
|
|
203
|
-
function
|
|
204
|
-
return
|
|
158
|
+
//#region src/cli/format.ts
|
|
159
|
+
function formatCliError(err) {
|
|
160
|
+
return isIdsError(err) ? `${err.code}: ${err.message}` : err instanceof Error ? err.message : String(err);
|
|
205
161
|
}
|
|
206
|
-
function
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
162
|
+
function formatWrappedInspectOutput(result) {
|
|
163
|
+
const inputLine = describeInputForm(result.input, result.canonical);
|
|
164
|
+
return [
|
|
165
|
+
`brand: ${result.brand}`,
|
|
166
|
+
`lookup-key: ${result.lookupKey.toString()}`,
|
|
167
|
+
`canonical: ${result.canonical}`,
|
|
168
|
+
`input: ${inputLine}`,
|
|
169
|
+
""
|
|
170
|
+
].join("\n");
|
|
212
171
|
}
|
|
213
|
-
function
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
172
|
+
function formatSignedInspectOutput(result) {
|
|
173
|
+
const relative = formatRelative(result.timestamp.getTime(), result.nowMs);
|
|
174
|
+
const inputLine = describeInputForm(result.input, result.canonical);
|
|
175
|
+
const lines = [`brand: ${result.brand}`, `timestamp: ${result.timestamp.toISOString()} (${relative})`];
|
|
176
|
+
lines.push(`verification: ${result.verification}`);
|
|
177
|
+
lines.push(`canonical: ${result.canonical}`, `input: ${inputLine}`, "");
|
|
178
|
+
return lines.join("\n");
|
|
217
179
|
}
|
|
218
|
-
function
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
180
|
+
function formatInspectOutput(result) {
|
|
181
|
+
const relative = formatRelative(result.timestamp.getTime(), result.nowMs);
|
|
182
|
+
const inputLine = describeInputForm(result.input, result.canonical);
|
|
183
|
+
return [
|
|
184
|
+
`brand: ${result.brand}`,
|
|
185
|
+
`timestamp: ${result.timestamp.toISOString()} (${relative})`,
|
|
186
|
+
`canonical: ${result.canonical}`,
|
|
187
|
+
`input: ${inputLine}`,
|
|
188
|
+
""
|
|
189
|
+
].join("\n");
|
|
225
190
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return err.message;
|
|
233
|
-
}
|
|
191
|
+
function describeInputForm(input, canonical) {
|
|
192
|
+
if (input === canonical) return "canonical";
|
|
193
|
+
const notes = [];
|
|
194
|
+
if (input !== input.toLowerCase()) notes.push("was uppercase");
|
|
195
|
+
if (/[ilo]/i.test(input.slice(4))) notes.push("used Crockford aliases");
|
|
196
|
+
return `not canonical (${notes.join(" + ")})`;
|
|
234
197
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
198
|
+
const msPerMinute = 60 * 1e3;
|
|
199
|
+
const msPerHour = 60 * msPerMinute;
|
|
200
|
+
const msPerDay = 24 * msPerHour;
|
|
201
|
+
const daysPerMonth = 30.44;
|
|
202
|
+
const monthsPerYear = 12;
|
|
203
|
+
function formatRelative(thenMs, nowMs) {
|
|
204
|
+
const diff = nowMs - thenMs;
|
|
205
|
+
const abs = Math.abs(diff);
|
|
206
|
+
const suffix = diff < 0 ? "from now" : "ago";
|
|
207
|
+
const head = headUnits(abs);
|
|
208
|
+
return head === "" ? "just now" : `${head} ${suffix}`;
|
|
245
209
|
}
|
|
246
|
-
function
|
|
247
|
-
|
|
210
|
+
function headUnits(abs) {
|
|
211
|
+
if (abs < msPerMinute) return "";
|
|
212
|
+
if (abs < msPerHour) return unit(Math.round(abs / msPerMinute), "minute");
|
|
213
|
+
if (abs < msPerDay) return unit(Math.round(abs / msPerHour), "hour");
|
|
214
|
+
if (abs < msPerDay * daysPerMonth) return unit(Math.round(abs / msPerDay), "day");
|
|
215
|
+
const totalMonths = Math.round(abs / (msPerDay * daysPerMonth));
|
|
216
|
+
if (totalMonths < monthsPerYear) return unit(totalMonths, "month");
|
|
217
|
+
const years = Math.floor(totalMonths / monthsPerYear);
|
|
218
|
+
const months = totalMonths % monthsPerYear;
|
|
219
|
+
return months === 0 ? unit(years, "year") : `${unit(years, "year")} ${unit(months, "month")}`;
|
|
248
220
|
}
|
|
249
|
-
|
|
250
|
-
return
|
|
221
|
+
function unit(n, name) {
|
|
222
|
+
return `${n} ${n === 1 ? name : `${name}s`}`;
|
|
251
223
|
}
|
|
252
224
|
//#endregion
|
|
253
|
-
//#region src/cli/
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
225
|
+
//#region src/cli/variants.ts
|
|
226
|
+
const timestampVariant = {
|
|
227
|
+
inspectMode: "readable",
|
|
228
|
+
construct(brand, opts) {
|
|
229
|
+
try {
|
|
230
|
+
return createTimestampId(brand, codecOpts(opts));
|
|
231
|
+
} catch (err) {
|
|
232
|
+
return formatCliError(err);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
259
235
|
};
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
236
|
+
const opaqueVariant = {
|
|
237
|
+
flag: "--opaque",
|
|
238
|
+
key: {
|
|
239
|
+
envVar: "IDS_KEY",
|
|
240
|
+
formatEnvVar: "IDS_KEY_FORMAT",
|
|
241
|
+
encode: encodeOpaqueKey,
|
|
242
|
+
decode: decodeOpaqueKey,
|
|
243
|
+
import: importOpaqueKey
|
|
244
|
+
},
|
|
245
|
+
inspectMode: "keyed-readable",
|
|
246
|
+
construct(brand, opts, key) {
|
|
247
|
+
try {
|
|
248
|
+
return createOpaqueTimestampId(brand, {
|
|
249
|
+
key,
|
|
250
|
+
...codecOpts(opts)
|
|
251
|
+
});
|
|
252
|
+
} catch (err) {
|
|
253
|
+
return formatCliError(err);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
const reverseVariant = {
|
|
258
|
+
flag: "--reverse",
|
|
259
|
+
inspectMode: "readable",
|
|
260
|
+
construct(brand, opts) {
|
|
261
|
+
try {
|
|
262
|
+
return createReverseTimestampId(brand, codecOpts(opts));
|
|
263
|
+
} catch (err) {
|
|
264
|
+
return formatCliError(err);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const wrappedVariant = {
|
|
269
|
+
flag: "--wrapped",
|
|
270
|
+
key: {
|
|
271
|
+
envVar: "IDS_WRAPPING_KEY",
|
|
272
|
+
formatEnvVar: "IDS_WRAPPING_KEY_FORMAT",
|
|
273
|
+
encode: encodeWrappingKey,
|
|
274
|
+
decode: decodeWrappingKey,
|
|
275
|
+
import: importWrappingKey
|
|
276
|
+
},
|
|
277
|
+
inspectMode: "unwrap",
|
|
278
|
+
extraFlags: ["--kind"],
|
|
279
|
+
construct(brand, _opts, key, values) {
|
|
280
|
+
const kind = parseKind(values ?? /* @__PURE__ */ new Map());
|
|
281
|
+
if (kind === void 0) return "--kind is required with --wrapped";
|
|
282
|
+
if (isKindError(kind)) return kind;
|
|
283
|
+
try {
|
|
284
|
+
return createWrappedKeyId(brand, {
|
|
285
|
+
kind,
|
|
286
|
+
keys: [key],
|
|
287
|
+
allowDuplicateBrand: true
|
|
288
|
+
});
|
|
289
|
+
} catch (err) {
|
|
290
|
+
return formatCliError(err);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
const signedVariant = {
|
|
295
|
+
flag: "--signed",
|
|
296
|
+
key: {
|
|
297
|
+
envVar: "IDS_SIGNING_KEY",
|
|
298
|
+
formatEnvVar: "IDS_SIGNING_KEY_FORMAT",
|
|
299
|
+
encode: encodeSigningKey,
|
|
300
|
+
decode: decodeSigningKey,
|
|
301
|
+
import: importSigningKey
|
|
302
|
+
},
|
|
303
|
+
inspectMode: "verify",
|
|
304
|
+
construct(brand, opts, key) {
|
|
305
|
+
try {
|
|
306
|
+
return createSignedTimestampId(brand, {
|
|
307
|
+
keys: [key],
|
|
308
|
+
...codecOpts(opts)
|
|
309
|
+
});
|
|
310
|
+
} catch (err) {
|
|
311
|
+
return formatCliError(err);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
const conflictPriorityOrder = [
|
|
316
|
+
signedVariant,
|
|
317
|
+
reverseVariant,
|
|
318
|
+
wrappedVariant,
|
|
319
|
+
opaqueVariant
|
|
320
|
+
];
|
|
321
|
+
const generatePolicy = {
|
|
322
|
+
default: timestampVariant,
|
|
323
|
+
selectable: [
|
|
324
|
+
opaqueVariant,
|
|
325
|
+
reverseVariant,
|
|
326
|
+
signedVariant
|
|
327
|
+
],
|
|
328
|
+
intrinsicFlags: ["--count", "-c"]
|
|
329
|
+
};
|
|
330
|
+
const inspectPolicy = {
|
|
331
|
+
default: timestampVariant,
|
|
332
|
+
selectable: [
|
|
333
|
+
reverseVariant,
|
|
334
|
+
wrappedVariant,
|
|
335
|
+
opaqueVariant,
|
|
336
|
+
signedVariant
|
|
337
|
+
],
|
|
338
|
+
intrinsicFlags: []
|
|
339
|
+
};
|
|
340
|
+
const keygenPolicy = {
|
|
341
|
+
default: opaqueVariant,
|
|
342
|
+
selectable: [wrappedVariant, signedVariant],
|
|
343
|
+
intrinsicFlags: ["--bits"]
|
|
344
|
+
};
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/cli/dispatch.ts
|
|
347
|
+
function deriveAllowedFlags(policy) {
|
|
348
|
+
const flags = new Set(policy.intrinsicFlags);
|
|
349
|
+
let hasKeyed = policy.default.key !== void 0;
|
|
350
|
+
for (const v of policy.selectable) {
|
|
351
|
+
if (v.flag !== void 0) flags.add(v.flag);
|
|
352
|
+
if (v.key !== void 0) hasKeyed = true;
|
|
353
|
+
if (v.extraFlags !== void 0) for (const f of v.extraFlags) flags.add(f);
|
|
354
|
+
}
|
|
355
|
+
if (hasKeyed) flags.add("--key-format");
|
|
356
|
+
return flags;
|
|
357
|
+
}
|
|
358
|
+
function resolveVariant(policy, flags) {
|
|
359
|
+
const selected = conflictPriorityOrder.filter((v) => policy.selectable.some((d) => d === v) && v.flag !== void 0 && flags.has(v.flag));
|
|
360
|
+
if (selected.length === 0) return policy.default;
|
|
361
|
+
if (selected.length === 1) return selected[0];
|
|
362
|
+
return `cannot use ${selected[0].flag} and ${selected[1].flag} together`;
|
|
363
|
+
}
|
|
364
|
+
async function buildCodec(variant, brand, values, opts) {
|
|
365
|
+
let key;
|
|
366
|
+
if (variant.key !== void 0) {
|
|
367
|
+
const format = parseKeyFormat(values, opts, variant.key);
|
|
368
|
+
if (isKeyFormatError(format)) return format;
|
|
369
|
+
const keyResult = await loadKey(opts, format, variant.key);
|
|
370
|
+
if (typeof keyResult === "string") return keyResult;
|
|
371
|
+
key = keyResult;
|
|
372
|
+
}
|
|
373
|
+
return variant.construct(brand, opts, key, values);
|
|
265
374
|
}
|
|
266
375
|
//#endregion
|
|
267
376
|
//#region src/cli/commands/generate.ts
|
|
268
|
-
function runGenerate(args, opts) {
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
"--opaque",
|
|
274
|
-
"--reverse",
|
|
275
|
-
"--signed",
|
|
276
|
-
"--key-format"
|
|
277
|
-
]));
|
|
377
|
+
async function runGenerate(args, opts) {
|
|
378
|
+
const allowedFlags = deriveAllowedFlags(generatePolicy);
|
|
379
|
+
const selectorFlags = new Set(generatePolicy.selectable.map((v) => v.flag).filter((f) => f !== void 0));
|
|
380
|
+
const { flags, values, positionals, errors } = splitFlags(args, new Set([...allowedFlags].filter((f) => !selectorFlags.has(f))));
|
|
381
|
+
const unsupported = unsupportedFlagForCommand("generate", flags, allowedFlags);
|
|
278
382
|
if (unsupported !== void 0) {
|
|
279
383
|
opts.stderr(unsupported + "\n");
|
|
280
|
-
return
|
|
384
|
+
return 1;
|
|
281
385
|
}
|
|
282
386
|
if (errors[0] !== void 0) {
|
|
283
387
|
opts.stderr(errors[0] + "\n");
|
|
284
|
-
return
|
|
388
|
+
return 1;
|
|
285
389
|
}
|
|
286
390
|
const extra = positionals[1];
|
|
287
391
|
if (extra !== void 0) {
|
|
288
392
|
opts.stderr(`unexpected argument: ${extra}\n`);
|
|
289
|
-
return
|
|
393
|
+
return 1;
|
|
290
394
|
}
|
|
291
395
|
const [brand] = positionals;
|
|
292
396
|
const count = parseCount(values);
|
|
293
397
|
if (typeof count === "string") {
|
|
294
398
|
opts.stderr(count + "\n");
|
|
295
|
-
return Promise.resolve(1);
|
|
296
|
-
}
|
|
297
|
-
const opaque = flags.has("--opaque");
|
|
298
|
-
const reverse = flags.has("--reverse");
|
|
299
|
-
const signed = flags.has("--signed");
|
|
300
|
-
if (reverse && opaque) {
|
|
301
|
-
opts.stderr("cannot use --reverse and --opaque together\n");
|
|
302
|
-
return Promise.resolve(1);
|
|
303
|
-
}
|
|
304
|
-
if (signed && opaque) {
|
|
305
|
-
opts.stderr("cannot use --signed and --opaque together\n");
|
|
306
|
-
return Promise.resolve(1);
|
|
307
|
-
}
|
|
308
|
-
if (signed && reverse) {
|
|
309
|
-
opts.stderr("cannot use --signed and --reverse together\n");
|
|
310
|
-
return Promise.resolve(1);
|
|
311
|
-
}
|
|
312
|
-
if (!opaque && !signed && flags.has("--key-format")) {
|
|
313
|
-
opts.stderr("--key-format requires --opaque or --signed\n");
|
|
314
|
-
return Promise.resolve(1);
|
|
315
|
-
}
|
|
316
|
-
if (opaque) {
|
|
317
|
-
const format = parseOpaqueKeyFormat(values, opts);
|
|
318
|
-
if (isKeyFormatError(format)) {
|
|
319
|
-
opts.stderr(format + "\n");
|
|
320
|
-
return Promise.resolve(1);
|
|
321
|
-
}
|
|
322
|
-
return runOpaqueGenerate(brand ?? "", count, format, opts);
|
|
323
|
-
}
|
|
324
|
-
if (signed) {
|
|
325
|
-
const format = parseSigningKeyFormat(values, opts);
|
|
326
|
-
if (isKeyFormatError(format)) {
|
|
327
|
-
opts.stderr(format + "\n");
|
|
328
|
-
return Promise.resolve(1);
|
|
329
|
-
}
|
|
330
|
-
return runSignedGenerate(brand ?? "", count, format, opts);
|
|
331
|
-
}
|
|
332
|
-
if (reverse) {
|
|
333
|
-
let codec;
|
|
334
|
-
try {
|
|
335
|
-
codec = createReverseTimestampId(brand ?? "", codecOpts(opts));
|
|
336
|
-
} catch (err) {
|
|
337
|
-
opts.stderr(formatCliError(err) + "\n");
|
|
338
|
-
return Promise.resolve(1);
|
|
339
|
-
}
|
|
340
|
-
for (let i = 0; i < count; i++) opts.stdout(codec.generate() + "\n");
|
|
341
|
-
return Promise.resolve(0);
|
|
342
|
-
}
|
|
343
|
-
let codec;
|
|
344
|
-
try {
|
|
345
|
-
codec = createTimestampId(brand ?? "", codecOpts(opts));
|
|
346
|
-
} catch (err) {
|
|
347
|
-
opts.stderr(formatCliError(err) + "\n");
|
|
348
|
-
return Promise.resolve(1);
|
|
349
|
-
}
|
|
350
|
-
for (let i = 0; i < count; i++) opts.stdout(codec.generate() + "\n");
|
|
351
|
-
return Promise.resolve(0);
|
|
352
|
-
}
|
|
353
|
-
async function runOpaqueGenerate(brand, count, format, opts) {
|
|
354
|
-
const keyResult = await loadOpaqueKey(opts, format);
|
|
355
|
-
if (typeof keyResult === "string") {
|
|
356
|
-
opts.stderr(keyResult + "\n");
|
|
357
399
|
return 1;
|
|
358
400
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
key: keyResult,
|
|
363
|
-
...codecOpts(opts)
|
|
364
|
-
});
|
|
365
|
-
} catch (err) {
|
|
366
|
-
opts.stderr(formatCliError(err) + "\n");
|
|
401
|
+
const variant = resolveVariant(generatePolicy, flags);
|
|
402
|
+
if (typeof variant === "string") {
|
|
403
|
+
opts.stderr(variant + "\n");
|
|
367
404
|
return 1;
|
|
368
405
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
async function runSignedGenerate(brand, count, format, opts) {
|
|
373
|
-
const keyResult = await loadSigningKey(opts, format);
|
|
374
|
-
if (typeof keyResult === "string") {
|
|
375
|
-
opts.stderr(keyResult + "\n");
|
|
406
|
+
if (variant.key === void 0 && flags.has("--key-format")) {
|
|
407
|
+
opts.stderr("--key-format requires --opaque or --signed\n");
|
|
376
408
|
return 1;
|
|
377
409
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
codec
|
|
381
|
-
keys: [keyResult],
|
|
382
|
-
allowDuplicateBrand: true,
|
|
383
|
-
...codecOpts(opts)
|
|
384
|
-
});
|
|
385
|
-
} catch (err) {
|
|
386
|
-
opts.stderr(formatCliError(err) + "\n");
|
|
410
|
+
const codec = await buildCodec(variant, brand ?? "", values, opts);
|
|
411
|
+
if (typeof codec === "string") {
|
|
412
|
+
opts.stderr(codec + "\n");
|
|
387
413
|
return 1;
|
|
388
414
|
}
|
|
389
415
|
for (let i = 0; i < count; i++) opts.stdout(await codec.generate() + "\n");
|
|
390
416
|
return 0;
|
|
391
417
|
}
|
|
392
418
|
//#endregion
|
|
393
|
-
//#region src/cli/wrapping-key.ts
|
|
394
|
-
const wrappingFacet = {
|
|
395
|
-
envVar: "IDS_WRAPPING_KEY",
|
|
396
|
-
formatEnvVar: "IDS_WRAPPING_KEY_FORMAT",
|
|
397
|
-
decode: decodeWrappingKey,
|
|
398
|
-
import: importWrappingKey
|
|
399
|
-
};
|
|
400
|
-
function parseWrappingKeyFormat(values, opts) {
|
|
401
|
-
return parseKeyFormat(values, opts, wrappingFacet);
|
|
402
|
-
}
|
|
403
|
-
async function loadWrappingKey(opts, format) {
|
|
404
|
-
return loadKey(opts, format, wrappingFacet);
|
|
405
|
-
}
|
|
406
|
-
//#endregion
|
|
407
419
|
//#region src/cli/usage.ts
|
|
408
420
|
function usage() {
|
|
409
421
|
return [
|
|
@@ -432,294 +444,167 @@ function usage() {
|
|
|
432
444
|
}
|
|
433
445
|
//#endregion
|
|
434
446
|
//#region src/cli/commands/inspect.ts
|
|
435
|
-
function runInspect(args, opts) {
|
|
436
|
-
const
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
"--reverse",
|
|
441
|
-
"--signed",
|
|
442
|
-
"--kind",
|
|
443
|
-
"--key-format"
|
|
444
|
-
]));
|
|
447
|
+
async function runInspect(args, opts) {
|
|
448
|
+
const allowedFlags = deriveAllowedFlags(inspectPolicy);
|
|
449
|
+
const selectorFlags = new Set(inspectPolicy.selectable.map((v) => v.flag).filter((f) => f !== void 0));
|
|
450
|
+
const { flags, values, positionals, errors } = splitFlags(args, new Set([...allowedFlags].filter((f) => !selectorFlags.has(f))));
|
|
451
|
+
const unsupported = unsupportedFlagForCommand("inspect", flags, allowedFlags);
|
|
445
452
|
if (unsupported !== void 0) {
|
|
446
453
|
opts.stderr(unsupported + "\n");
|
|
447
|
-
return
|
|
454
|
+
return 1;
|
|
448
455
|
}
|
|
449
456
|
if (errors[0] !== void 0) {
|
|
450
457
|
opts.stderr(errors[0] + "\n");
|
|
451
|
-
return
|
|
458
|
+
return 1;
|
|
452
459
|
}
|
|
453
460
|
const [input] = positionals;
|
|
454
461
|
if (input === void 0) {
|
|
455
462
|
opts.stderr(usage());
|
|
456
|
-
return
|
|
463
|
+
return 1;
|
|
457
464
|
}
|
|
458
465
|
const extra = positionals[1];
|
|
459
466
|
if (extra !== void 0) {
|
|
460
467
|
opts.stderr(`unexpected argument: ${extra}\n`);
|
|
461
|
-
return
|
|
462
|
-
}
|
|
463
|
-
const opaque = flags.has("--opaque");
|
|
464
|
-
const wrapped = flags.has("--wrapped");
|
|
465
|
-
const reverse = flags.has("--reverse");
|
|
466
|
-
const signed = flags.has("--signed");
|
|
467
|
-
if (opaque && wrapped) {
|
|
468
|
-
opts.stderr("cannot use --wrapped and --opaque together\n");
|
|
469
|
-
return Promise.resolve(1);
|
|
470
|
-
}
|
|
471
|
-
if (reverse && opaque) {
|
|
472
|
-
opts.stderr("cannot use --reverse and --opaque together\n");
|
|
473
|
-
return Promise.resolve(1);
|
|
474
|
-
}
|
|
475
|
-
if (reverse && wrapped) {
|
|
476
|
-
opts.stderr("cannot use --reverse and --wrapped together\n");
|
|
477
|
-
return Promise.resolve(1);
|
|
478
|
-
}
|
|
479
|
-
if (signed && opaque) {
|
|
480
|
-
opts.stderr("cannot use --signed and --opaque together\n");
|
|
481
|
-
return Promise.resolve(1);
|
|
482
|
-
}
|
|
483
|
-
if (signed && wrapped) {
|
|
484
|
-
opts.stderr("cannot use --signed and --wrapped together\n");
|
|
485
|
-
return Promise.resolve(1);
|
|
468
|
+
return 1;
|
|
486
469
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
470
|
+
const variant = resolveVariant(inspectPolicy, flags);
|
|
471
|
+
if (typeof variant === "string") {
|
|
472
|
+
opts.stderr(variant + "\n");
|
|
473
|
+
return 1;
|
|
490
474
|
}
|
|
491
|
-
if (
|
|
475
|
+
if (variant.key === void 0 && flags.has("--key-format")) {
|
|
492
476
|
opts.stderr("--key-format requires --opaque, --wrapped, or --signed\n");
|
|
493
|
-
return
|
|
477
|
+
return 1;
|
|
494
478
|
}
|
|
495
479
|
const brand = input.slice(0, 3).toLowerCase();
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
return Promise.resolve(1);
|
|
505
|
-
}
|
|
506
|
-
const format = parseWrappingKeyFormat(values, opts);
|
|
507
|
-
if (isKeyFormatError(format)) {
|
|
508
|
-
opts.stderr(format + "\n");
|
|
509
|
-
return Promise.resolve(1);
|
|
510
|
-
}
|
|
511
|
-
return runWrappedInspect(brand, input, kind, format, opts);
|
|
512
|
-
}
|
|
513
|
-
if (opaque) {
|
|
514
|
-
const format = parseOpaqueKeyFormat(values, opts);
|
|
515
|
-
if (isKeyFormatError(format)) {
|
|
516
|
-
opts.stderr(format + "\n");
|
|
517
|
-
return Promise.resolve(1);
|
|
518
|
-
}
|
|
519
|
-
return runOpaqueInspect(brand, input, format, opts);
|
|
520
|
-
}
|
|
521
|
-
if (signed) {
|
|
522
|
-
const format = parseSigningKeyFormat(values, opts);
|
|
523
|
-
if (isKeyFormatError(format)) {
|
|
524
|
-
opts.stderr(format + "\n");
|
|
525
|
-
return Promise.resolve(1);
|
|
480
|
+
let verifyTimestamp;
|
|
481
|
+
let verifyCanonical;
|
|
482
|
+
let verifyNowMs;
|
|
483
|
+
if (variant.inspectMode === "verify") {
|
|
484
|
+
const fmtCheck = parseKeyFormat(values, opts, variant.key);
|
|
485
|
+
if (isKeyFormatError(fmtCheck)) {
|
|
486
|
+
opts.stderr(fmtCheck + "\n");
|
|
487
|
+
return 1;
|
|
526
488
|
}
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
if (reverse) {
|
|
530
|
-
let reverseCodec;
|
|
489
|
+
let tsCodec;
|
|
531
490
|
try {
|
|
532
|
-
|
|
491
|
+
tsCodec = createTimestampId(brand, codecOpts(opts));
|
|
533
492
|
} catch (err) {
|
|
534
493
|
opts.stderr(formatCliError(err) + "\n");
|
|
535
|
-
return
|
|
494
|
+
return 1;
|
|
536
495
|
}
|
|
537
|
-
const
|
|
538
|
-
if (
|
|
539
|
-
opts.stderr(
|
|
540
|
-
return
|
|
496
|
+
const structValidation = tsCodec["~standard"].validate(input);
|
|
497
|
+
if (structValidation.issues) {
|
|
498
|
+
opts.stderr(structValidation.issues[0].message + "\n");
|
|
499
|
+
return 1;
|
|
541
500
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
opts.stdout(formatInspectOutput({
|
|
546
|
-
brand,
|
|
547
|
-
timestamp: reverseTimestamp,
|
|
548
|
-
canonical: reverseCanonical,
|
|
549
|
-
input,
|
|
550
|
-
nowMs: reverseNowMs
|
|
551
|
-
}));
|
|
552
|
-
return Promise.resolve(0);
|
|
553
|
-
}
|
|
554
|
-
let codec;
|
|
555
|
-
try {
|
|
556
|
-
codec = createTimestampId(brand, codecOpts(opts));
|
|
557
|
-
} catch (err) {
|
|
558
|
-
opts.stderr(formatCliError(err) + "\n");
|
|
559
|
-
return Promise.resolve(1);
|
|
501
|
+
verifyCanonical = structValidation.value;
|
|
502
|
+
verifyTimestamp = tsCodec.extractTimestamp(verifyCanonical);
|
|
503
|
+
verifyNowMs = (opts.now ?? Date.now)();
|
|
560
504
|
}
|
|
561
|
-
const
|
|
562
|
-
if (
|
|
563
|
-
|
|
564
|
-
return Promise.resolve(1);
|
|
565
|
-
}
|
|
566
|
-
const canonical = validation.value;
|
|
567
|
-
const timestamp = codec.extractTimestamp(canonical);
|
|
568
|
-
const nowMs = (opts.now ?? Date.now)();
|
|
569
|
-
opts.stdout(formatInspectOutput({
|
|
570
|
-
brand,
|
|
571
|
-
timestamp,
|
|
572
|
-
canonical,
|
|
573
|
-
input,
|
|
574
|
-
nowMs
|
|
575
|
-
}));
|
|
576
|
-
return Promise.resolve(0);
|
|
577
|
-
}
|
|
578
|
-
async function runWrappedInspect(brand, input, kind, format, opts) {
|
|
579
|
-
const keyResult = await loadWrappingKey(opts, format);
|
|
580
|
-
if (typeof keyResult === "string") {
|
|
581
|
-
opts.stderr(keyResult + "\n");
|
|
582
|
-
return 1;
|
|
583
|
-
}
|
|
584
|
-
let codec;
|
|
585
|
-
try {
|
|
586
|
-
codec = createWrappedKeyId(brand, {
|
|
587
|
-
kind,
|
|
588
|
-
keys: [keyResult],
|
|
589
|
-
allowDuplicateBrand: true
|
|
590
|
-
});
|
|
591
|
-
} catch (err) {
|
|
592
|
-
opts.stderr(formatCliError(err) + "\n");
|
|
593
|
-
return 1;
|
|
594
|
-
}
|
|
595
|
-
const validation = codec["~standard"].validate(input);
|
|
596
|
-
if (validation.issues) {
|
|
597
|
-
opts.stderr(validation.issues[0].message + "\n");
|
|
598
|
-
return 1;
|
|
599
|
-
}
|
|
600
|
-
const canonical = validation.value;
|
|
601
|
-
let lookupKey;
|
|
602
|
-
try {
|
|
603
|
-
lookupKey = await codec.unwrap(canonical);
|
|
604
|
-
} catch (err) {
|
|
605
|
-
opts.stderr(formatCliError(err) + "\n");
|
|
606
|
-
return 1;
|
|
607
|
-
}
|
|
608
|
-
opts.stdout(formatWrappedInspectOutput({
|
|
609
|
-
brand,
|
|
610
|
-
lookupKey,
|
|
611
|
-
canonical,
|
|
612
|
-
input
|
|
613
|
-
}));
|
|
614
|
-
return 0;
|
|
615
|
-
}
|
|
616
|
-
async function runOpaqueInspect(brand, input, format, opts) {
|
|
617
|
-
const keyResult = await loadOpaqueKey(opts, format);
|
|
618
|
-
if (typeof keyResult === "string") {
|
|
619
|
-
opts.stderr(keyResult + "\n");
|
|
620
|
-
return 1;
|
|
621
|
-
}
|
|
622
|
-
let codec;
|
|
623
|
-
try {
|
|
624
|
-
codec = createOpaqueTimestampId(brand, {
|
|
625
|
-
key: keyResult,
|
|
626
|
-
...codecOpts(opts)
|
|
627
|
-
});
|
|
628
|
-
} catch (err) {
|
|
629
|
-
opts.stderr(formatCliError(err) + "\n");
|
|
630
|
-
return 1;
|
|
631
|
-
}
|
|
632
|
-
const validation = codec["~standard"].validate(input);
|
|
633
|
-
if (validation.issues) {
|
|
634
|
-
opts.stderr(validation.issues[0].message + "\n");
|
|
635
|
-
return 1;
|
|
636
|
-
}
|
|
637
|
-
const canonical = validation.value;
|
|
638
|
-
const timestamp = await codec.extractTimestamp(canonical);
|
|
639
|
-
const nowMs = (opts.now ?? Date.now)();
|
|
640
|
-
opts.stderr("note: timestamp assumes IDS_KEY matches the key used at generation; a wrong key yields a plausible but incorrect timestamp\n");
|
|
641
|
-
opts.stdout(formatInspectOutput({
|
|
642
|
-
brand,
|
|
643
|
-
timestamp,
|
|
644
|
-
canonical,
|
|
645
|
-
input,
|
|
646
|
-
nowMs
|
|
647
|
-
}));
|
|
648
|
-
return 0;
|
|
649
|
-
}
|
|
650
|
-
async function runSignedInspect(brand, input, format, opts) {
|
|
651
|
-
let structCodec;
|
|
652
|
-
try {
|
|
653
|
-
structCodec = createTimestampId(brand, codecOpts(opts));
|
|
654
|
-
} catch (err) {
|
|
655
|
-
opts.stderr(formatCliError(err) + "\n");
|
|
656
|
-
return 1;
|
|
657
|
-
}
|
|
658
|
-
const validation = structCodec["~standard"].validate(input);
|
|
659
|
-
if (validation.issues) {
|
|
660
|
-
opts.stderr(validation.issues[0].message + "\n");
|
|
661
|
-
return 1;
|
|
662
|
-
}
|
|
663
|
-
const canonical = validation.value;
|
|
664
|
-
const timestamp = structCodec.extractTimestamp(canonical);
|
|
665
|
-
const nowMs = (opts.now ?? Date.now)();
|
|
666
|
-
if ((opts.env ?? process.env).IDS_SIGNING_KEY === void 0) {
|
|
667
|
-
opts.stdout(formatSignedInspectOutput({
|
|
505
|
+
const codecOrError = await buildCodec(variant, brand, values, opts);
|
|
506
|
+
if (typeof codecOrError === "string") {
|
|
507
|
+
if (variant.inspectMode === "verify") opts.stdout(formatSignedInspectOutput({
|
|
668
508
|
brand,
|
|
669
|
-
timestamp,
|
|
670
|
-
canonical,
|
|
509
|
+
timestamp: verifyTimestamp,
|
|
510
|
+
canonical: verifyCanonical,
|
|
671
511
|
input,
|
|
672
|
-
nowMs
|
|
512
|
+
nowMs: verifyNowMs,
|
|
513
|
+
verification: "unavailable"
|
|
673
514
|
}));
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
const keyResult = await loadSigningKey(opts, format);
|
|
677
|
-
if (typeof keyResult === "string") {
|
|
678
|
-
opts.stderr(keyResult + "\n");
|
|
515
|
+
opts.stderr(codecOrError + "\n");
|
|
679
516
|
return 1;
|
|
680
517
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
if (!verifyResult.ok) {
|
|
687
|
-
/* v8 ignore next 4 -- defensive: both codecs share the same wire parse so ParseError
|
|
688
|
-
is unreachable after the createTimestampId pre-validation above passes */
|
|
689
|
-
if (verifyResult.error !== "verification_failed") {
|
|
690
|
-
opts.stderr(verifyResult.error + "\n");
|
|
518
|
+
let canonical;
|
|
519
|
+
if (variant.inspectMode !== "verify") {
|
|
520
|
+
const validation = codecOrError["~standard"].validate(input);
|
|
521
|
+
if (validation.issues) {
|
|
522
|
+
opts.stderr(validation.issues[0].message + "\n");
|
|
691
523
|
return 1;
|
|
692
524
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
nowMs
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
525
|
+
canonical = validation.value;
|
|
526
|
+
}
|
|
527
|
+
switch (variant.inspectMode) {
|
|
528
|
+
case "readable": {
|
|
529
|
+
const timestamp = codecOrError.extractTimestamp(canonical);
|
|
530
|
+
const nowMs = (opts.now ?? Date.now)();
|
|
531
|
+
opts.stdout(formatInspectOutput({
|
|
532
|
+
brand,
|
|
533
|
+
timestamp,
|
|
534
|
+
canonical,
|
|
535
|
+
input,
|
|
536
|
+
nowMs
|
|
537
|
+
}));
|
|
538
|
+
return 0;
|
|
539
|
+
}
|
|
540
|
+
case "keyed-readable": {
|
|
541
|
+
const timestamp = await codecOrError.extractTimestamp(canonical);
|
|
542
|
+
const nowMs = (opts.now ?? Date.now)();
|
|
543
|
+
opts.stderr("note: timestamp assumes IDS_KEY matches the key used at generation; a wrong key yields a plausible but incorrect timestamp\n");
|
|
544
|
+
opts.stdout(formatInspectOutput({
|
|
545
|
+
brand,
|
|
546
|
+
timestamp,
|
|
547
|
+
canonical,
|
|
548
|
+
input,
|
|
549
|
+
nowMs
|
|
550
|
+
}));
|
|
551
|
+
return 0;
|
|
552
|
+
}
|
|
553
|
+
case "unwrap": {
|
|
554
|
+
let lookupKey;
|
|
555
|
+
try {
|
|
556
|
+
lookupKey = await codecOrError.unwrap(canonical);
|
|
557
|
+
} catch (err) {
|
|
558
|
+
opts.stderr(formatCliError(err) + "\n");
|
|
559
|
+
return 1;
|
|
560
|
+
}
|
|
561
|
+
opts.stdout(formatWrappedInspectOutput({
|
|
562
|
+
brand,
|
|
563
|
+
lookupKey,
|
|
564
|
+
canonical,
|
|
565
|
+
input
|
|
566
|
+
}));
|
|
567
|
+
return 0;
|
|
568
|
+
}
|
|
569
|
+
case "verify": {
|
|
570
|
+
const verifyResult = await codecOrError.safeVerify(input);
|
|
571
|
+
if (!verifyResult.ok) {
|
|
572
|
+
/* v8 ignore next 4 -- defensive: both codecs share the same wire parse so ParseError
|
|
573
|
+
is unreachable after the createTimestampId pre-validation above passes */
|
|
574
|
+
if (verifyResult.error !== "verification_failed") {
|
|
575
|
+
opts.stderr(verifyResult.error + "\n");
|
|
576
|
+
return 1;
|
|
577
|
+
}
|
|
578
|
+
opts.stdout(formatSignedInspectOutput({
|
|
579
|
+
brand,
|
|
580
|
+
timestamp: verifyTimestamp,
|
|
581
|
+
canonical: verifyCanonical,
|
|
582
|
+
input,
|
|
583
|
+
nowMs: verifyNowMs,
|
|
584
|
+
verification: "failed"
|
|
585
|
+
}));
|
|
586
|
+
opts.stderr("verification_failed: verification failed\n");
|
|
587
|
+
return 1;
|
|
588
|
+
}
|
|
589
|
+
opts.stdout(formatSignedInspectOutput({
|
|
590
|
+
brand,
|
|
591
|
+
timestamp: verifyTimestamp,
|
|
592
|
+
canonical: verifyResult.id,
|
|
593
|
+
input,
|
|
594
|
+
nowMs: verifyNowMs,
|
|
595
|
+
verification: "ok"
|
|
596
|
+
}));
|
|
597
|
+
return 0;
|
|
598
|
+
}
|
|
702
599
|
}
|
|
703
|
-
opts.stdout(formatSignedInspectOutput({
|
|
704
|
-
brand,
|
|
705
|
-
timestamp,
|
|
706
|
-
canonical: verifyResult.id,
|
|
707
|
-
input,
|
|
708
|
-
nowMs,
|
|
709
|
-
verification: "ok"
|
|
710
|
-
}));
|
|
711
|
-
return 0;
|
|
712
600
|
}
|
|
713
601
|
//#endregion
|
|
714
602
|
//#region src/cli/commands/keygen.ts
|
|
715
603
|
function runKeygen(args, opts) {
|
|
716
|
-
const
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
"--bits",
|
|
721
|
-
"--key-format"
|
|
722
|
-
]));
|
|
604
|
+
const allowedFlags = deriveAllowedFlags(keygenPolicy);
|
|
605
|
+
const variantExtraFlags = new Set(keygenPolicy.selectable.flatMap((v) => v.extraFlags ?? []));
|
|
606
|
+
const { flags, values, positionals, errors } = splitFlags(args, allowedFlags);
|
|
607
|
+
const unsupported = unsupportedFlagForCommand("keygen", flags, new Set([...allowedFlags].filter((f) => !variantExtraFlags.has(f))));
|
|
723
608
|
if (unsupported !== void 0) {
|
|
724
609
|
opts.stderr(unsupported + "\n");
|
|
725
610
|
return Promise.resolve(1);
|
|
@@ -733,10 +618,9 @@ function runKeygen(args, opts) {
|
|
|
733
618
|
opts.stderr(`unexpected argument: ${extra}\n`);
|
|
734
619
|
return Promise.resolve(1);
|
|
735
620
|
}
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
opts.stderr("cannot use --signed and --wrapped together\n");
|
|
621
|
+
const variant = resolveVariant(keygenPolicy, flags);
|
|
622
|
+
if (typeof variant === "string") {
|
|
623
|
+
opts.stderr(variant + "\n");
|
|
740
624
|
return Promise.resolve(1);
|
|
741
625
|
}
|
|
742
626
|
const bits = parseBits(values);
|
|
@@ -744,18 +628,19 @@ function runKeygen(args, opts) {
|
|
|
744
628
|
opts.stderr(bits + "\n");
|
|
745
629
|
return Promise.resolve(1);
|
|
746
630
|
}
|
|
747
|
-
const format =
|
|
631
|
+
const format = parseKeyFormatFromFlag(values);
|
|
748
632
|
if (isKeyFormatError(format)) {
|
|
749
633
|
opts.stderr(format + "\n");
|
|
750
634
|
return Promise.resolve(1);
|
|
751
635
|
}
|
|
636
|
+
/* v8 ignore next 4 -- defensive guard; all keygenPolicy variants have key defined */
|
|
637
|
+
if (variant.key === void 0) {
|
|
638
|
+
opts.stderr("internal: keygen policy variant has no key facet\n");
|
|
639
|
+
return Promise.resolve(1);
|
|
640
|
+
}
|
|
752
641
|
const bytes = new Uint8Array(bits / 8);
|
|
753
642
|
crypto.getRandomValues(bytes);
|
|
754
|
-
|
|
755
|
-
if (signed) encoded = encodeSigningKey(bytes, format);
|
|
756
|
-
else if (wrapped) encoded = encodeWrappingKey(bytes, format);
|
|
757
|
-
else encoded = encodeOpaqueKey(bytes, format);
|
|
758
|
-
opts.stdout(encoded + "\n");
|
|
643
|
+
opts.stdout(variant.key.encode(bytes, format) + "\n");
|
|
759
644
|
return Promise.resolve(0);
|
|
760
645
|
}
|
|
761
646
|
//#endregion
|