sec-ry 1.1.5
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/dist/cli.bundle.js +7211 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +37 -0
- package/dist/cmd/changelog.d.ts +2 -0
- package/dist/cmd/changelog.js +726 -0
- package/dist/cmd/denc.d.ts +2 -0
- package/dist/cmd/denc.js +74 -0
- package/dist/cmd/enc.d.ts +2 -0
- package/dist/cmd/enc.js +225 -0
- package/dist/cmd/example.d.ts +1 -0
- package/dist/cmd/example.js +92 -0
- package/dist/cmd/fp.d.ts +2 -0
- package/dist/cmd/fp.js +29 -0
- package/dist/cmd/gen.d.ts +2 -0
- package/dist/cmd/gen.js +76 -0
- package/dist/cmd/history.d.ts +2 -0
- package/dist/cmd/history.js +25 -0
- package/dist/cmd/info.d.ts +2 -0
- package/dist/cmd/info.js +28 -0
- package/dist/cmd/root.d.ts +1 -0
- package/dist/cmd/root.js +191 -0
- package/dist/cmd/seal.d.ts +3 -0
- package/dist/cmd/seal.js +81 -0
- package/dist/cmd/upgrade.d.ts +1 -0
- package/dist/cmd/upgrade.js +44 -0
- package/dist/cmd/verify.d.ts +2 -0
- package/dist/cmd/verify.js +30 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +27 -0
- package/dist/lib/args.d.ts +8 -0
- package/dist/lib/args.js +43 -0
- package/dist/lib/clip.d.ts +2 -0
- package/dist/lib/clip.js +39 -0
- package/dist/lib/crypto.d.ts +29 -0
- package/dist/lib/crypto.js +312 -0
- package/dist/lib/env.d.ts +13 -0
- package/dist/lib/env.js +42 -0
- package/dist/lib/errors.d.ts +1 -0
- package/dist/lib/errors.js +61 -0
- package/dist/lib/history.d.ts +9 -0
- package/dist/lib/history.js +35 -0
- package/dist/lib/password.d.ts +2 -0
- package/dist/lib/password.js +31 -0
- package/dist/lib/rc.d.ts +7 -0
- package/dist/lib/rc.js +45 -0
- package/dist/lib/ui.d.ts +21 -0
- package/dist/lib/ui.js +137 -0
- package/package.json +44 -0
package/dist/cmd/denc.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cmdDenc = cmdDenc;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const crypto_1 = require("../lib/crypto");
|
|
6
|
+
const password_1 = require("../lib/password");
|
|
7
|
+
const ui_1 = require("../lib/ui");
|
|
8
|
+
const clip_1 = require("../lib/clip");
|
|
9
|
+
const rc_1 = require("../lib/rc");
|
|
10
|
+
const history_1 = require("../lib/history");
|
|
11
|
+
const args_1 = require("../lib/args");
|
|
12
|
+
function cmdDenc(parsed) {
|
|
13
|
+
const rc = (0, rc_1.loadRC)();
|
|
14
|
+
const token = parsed.positional[0];
|
|
15
|
+
if (!token)
|
|
16
|
+
(0, ui_1.err)("missing <token> argument");
|
|
17
|
+
if (token.startsWith("secry$") || token.startsWith("sec$")) {
|
|
18
|
+
(0, ui_1.err)("token uses '$' separator — did you use double quotes?\n use single quotes: secry denc 'secry:v2:...' -sw password", 2);
|
|
19
|
+
}
|
|
20
|
+
const rawPw = (0, args_1.getFlag)(parsed.flags, "sw") ?? rc.password;
|
|
21
|
+
if (!rawPw)
|
|
22
|
+
(0, ui_1.err)("-sw <password> is required (or set 'password' in .secryrc)");
|
|
23
|
+
const password = (0, password_1.resolvePassword)(rawPw);
|
|
24
|
+
const pwSource = (0, password_1.describePasswordSource)(rawPw);
|
|
25
|
+
const outFile = (0, args_1.getFlag)(parsed.flags, "out", "o");
|
|
26
|
+
const useClip = (0, args_1.hasFlag)(parsed.flags, "clip") || !!rc.clip;
|
|
27
|
+
const t0 = Date.now();
|
|
28
|
+
let result;
|
|
29
|
+
try {
|
|
30
|
+
result = (0, crypto_1.decrypt)(token, password);
|
|
31
|
+
}
|
|
32
|
+
catch (ex) {
|
|
33
|
+
const code = ex.code ?? "";
|
|
34
|
+
if (code === "ERR_EXPIRED")
|
|
35
|
+
(0, ui_1.err)(`token expired at ${ex.expiredAt?.toISOString()}`, 2);
|
|
36
|
+
if (code === "ERR_AUTH")
|
|
37
|
+
(0, ui_1.err)("wrong password or token has been tampered with", 2);
|
|
38
|
+
if (code === "ERR_FORMAT")
|
|
39
|
+
(0, ui_1.err)("value does not look like a secry or sec token", 2);
|
|
40
|
+
if (code === "ERR_MALFORMED")
|
|
41
|
+
(0, ui_1.err)("token is malformed or truncated", 2);
|
|
42
|
+
(0, ui_1.err)(ex.message, 2);
|
|
43
|
+
}
|
|
44
|
+
const elapsed = Date.now() - t0;
|
|
45
|
+
(0, history_1.recordHistory)("denc", token);
|
|
46
|
+
(0, ui_1.section)("decrypt");
|
|
47
|
+
(0, ui_1.box)([
|
|
48
|
+
{ label: "token", value: token.length > 40 ? token.slice(0, 40) + "…" : token },
|
|
49
|
+
{ label: "pw", value: pwSource },
|
|
50
|
+
{ label: "algo", value: result.tokenVersion === 2 ? "ChaCha20-Poly1305" : "AES-256-GCM" },
|
|
51
|
+
...(result.expiresAt ? [{ label: "expires", value: result.expiresAt.toISOString() }] : []),
|
|
52
|
+
{ label: "result", value: result.plaintext.length > 52 ? result.plaintext.slice(0, 52) + "…" : result.plaintext },
|
|
53
|
+
]);
|
|
54
|
+
(0, ui_1.ms)(elapsed);
|
|
55
|
+
if (outFile) {
|
|
56
|
+
try {
|
|
57
|
+
(0, fs_1.writeFileSync)(outFile, result.plaintext, "utf8");
|
|
58
|
+
(0, ui_1.info)(`saved to ${outFile}`);
|
|
59
|
+
process.stderr.write("\n");
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
(0, ui_1.err)(`could not write file: ${outFile}`, 2);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (useClip) {
|
|
66
|
+
const copied = (0, clip_1.copyToClipboard)(result.plaintext);
|
|
67
|
+
if (copied)
|
|
68
|
+
(0, ui_1.info)("copied to clipboard");
|
|
69
|
+
else
|
|
70
|
+
(0, ui_1.warn)("--clip: no clipboard tool found");
|
|
71
|
+
}
|
|
72
|
+
if (!outFile)
|
|
73
|
+
(0, ui_1.data)(result.plaintext);
|
|
74
|
+
}
|
package/dist/cmd/enc.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cmdEnc = cmdEnc;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const readline_1 = require("readline");
|
|
6
|
+
const crypto_1 = require("../lib/crypto");
|
|
7
|
+
const password_1 = require("../lib/password");
|
|
8
|
+
const ui_1 = require("../lib/ui");
|
|
9
|
+
const clip_1 = require("../lib/clip");
|
|
10
|
+
const rc_1 = require("../lib/rc");
|
|
11
|
+
const history_1 = require("../lib/history");
|
|
12
|
+
const env_1 = require("../lib/env");
|
|
13
|
+
const args_1 = require("../lib/args");
|
|
14
|
+
const _MAX = 20 * 1024 * 1024;
|
|
15
|
+
const _WARN = 5 * 1024 * 1024;
|
|
16
|
+
const _NO_PW = "__secry_nopassword__";
|
|
17
|
+
const _ALGO_LABEL = {
|
|
18
|
+
1: "AES-256-GCM / scrypt",
|
|
19
|
+
2: "ChaCha20-Poly1305 / scrypt",
|
|
20
|
+
3: "AES-256-CBC / scrypt",
|
|
21
|
+
};
|
|
22
|
+
function _detectEncoding(buf) {
|
|
23
|
+
if (buf[0] === 0xff && buf[1] === 0xfe)
|
|
24
|
+
return "utf16le";
|
|
25
|
+
if (buf[0] === 0xfe && buf[1] === 0xff)
|
|
26
|
+
return "utf16le";
|
|
27
|
+
if (buf[0] === 0xef && buf[1] === 0xbb && buf[2] === 0xbf)
|
|
28
|
+
return "utf8";
|
|
29
|
+
try {
|
|
30
|
+
const str = buf.toString("utf8");
|
|
31
|
+
if (Buffer.from(str, "utf8").equals(buf))
|
|
32
|
+
return "utf8";
|
|
33
|
+
}
|
|
34
|
+
catch { }
|
|
35
|
+
return "latin1";
|
|
36
|
+
}
|
|
37
|
+
function _readFileText(path) {
|
|
38
|
+
const buf = (0, fs_1.readFileSync)(path);
|
|
39
|
+
const encoding = _detectEncoding(buf);
|
|
40
|
+
const start = (encoding === "utf8" && buf[0] === 0xef) ? 3
|
|
41
|
+
: (encoding === "utf16le") ? 2
|
|
42
|
+
: 0;
|
|
43
|
+
const text = buf.subarray(start).toString(encoding);
|
|
44
|
+
return { text, encoding };
|
|
45
|
+
}
|
|
46
|
+
async function _confirm(msg) {
|
|
47
|
+
return new Promise((res) => {
|
|
48
|
+
const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stderr });
|
|
49
|
+
rl.question(msg, (a) => { rl.close(); res(a.trim().toLowerCase() === "y"); });
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function _askPassword() {
|
|
53
|
+
return new Promise((res) => {
|
|
54
|
+
process.stderr.write(` \x1b[36m?\x1b[0m password: `);
|
|
55
|
+
process.stdin.setRawMode?.(true);
|
|
56
|
+
let buf = "";
|
|
57
|
+
process.stdin.resume();
|
|
58
|
+
process.stdin.setEncoding("utf8");
|
|
59
|
+
const onData = (ch) => {
|
|
60
|
+
if (ch === "\r" || ch === "\n") {
|
|
61
|
+
process.stdin.setRawMode?.(false);
|
|
62
|
+
process.stdin.pause();
|
|
63
|
+
process.stdin.removeListener("data", onData);
|
|
64
|
+
process.stderr.write("\n");
|
|
65
|
+
res(buf);
|
|
66
|
+
}
|
|
67
|
+
else if (ch === "\u0003") {
|
|
68
|
+
process.stderr.write("\naborted.\n");
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
else if (ch === "\u007f") {
|
|
72
|
+
buf = buf.slice(0, -1);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
buf += ch;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
process.stdin.on("data", onData);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async function _interactive() {
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
process.stderr.write(` \x1b[36m?\x1b[0m enter text: `);
|
|
84
|
+
let text = "";
|
|
85
|
+
if (env_1.env.hasRawMode) {
|
|
86
|
+
process.stdin.setRawMode(true);
|
|
87
|
+
process.stdin.resume();
|
|
88
|
+
process.stdin.setEncoding("utf8");
|
|
89
|
+
const onData = (ch) => {
|
|
90
|
+
if (ch === "\r" || ch === "\n") {
|
|
91
|
+
process.stdin.setRawMode(false);
|
|
92
|
+
process.stdin.pause();
|
|
93
|
+
process.stdin.removeListener("data", onData);
|
|
94
|
+
process.stderr.write("\n");
|
|
95
|
+
resolve(text);
|
|
96
|
+
}
|
|
97
|
+
else if (ch === "\u0003") {
|
|
98
|
+
process.stderr.write("\naborted.\n");
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
else if (ch === "\u007f") {
|
|
102
|
+
text = text.slice(0, -1);
|
|
103
|
+
process.stderr.write("\b \b");
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
text += ch;
|
|
107
|
+
process.stderr.write("·");
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
process.stdin.on("data", onData);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stderr });
|
|
114
|
+
rl.question("", (a) => { rl.close(); resolve(a); });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function _readInput(raw) {
|
|
119
|
+
if (raw.startsWith("@")) {
|
|
120
|
+
const path = raw.slice(1).trim();
|
|
121
|
+
if (!(0, fs_1.existsSync)(path))
|
|
122
|
+
(0, ui_1.err)(`file not found: ${path}`, 2);
|
|
123
|
+
const stat = (0, fs_1.statSync)(path);
|
|
124
|
+
if (stat.size > _MAX)
|
|
125
|
+
(0, ui_1.err)(`file too large: ${(stat.size / 1024 / 1024).toFixed(1)} MB. max 20 MB`, 2);
|
|
126
|
+
const { text, encoding } = _readFileText(path);
|
|
127
|
+
return { text, source: `file(${path})`, bytes: stat.size, encoding };
|
|
128
|
+
}
|
|
129
|
+
const bytes = Buffer.byteLength(raw, "utf8");
|
|
130
|
+
return { text: raw, source: "arg", bytes };
|
|
131
|
+
}
|
|
132
|
+
async function cmdEnc(parsed) {
|
|
133
|
+
const rc = (0, rc_1.loadRC)();
|
|
134
|
+
const rawPw = (0, args_1.getFlag)(parsed.flags, "sw") ?? rc.password;
|
|
135
|
+
let password;
|
|
136
|
+
let pwSource;
|
|
137
|
+
if (!rawPw) {
|
|
138
|
+
const proceed = await _confirm(`\n \x1b[33m!\x1b[0m Proceed without a password? [y/n]: `);
|
|
139
|
+
if (!proceed) {
|
|
140
|
+
password = await _askPassword();
|
|
141
|
+
if (password.length < 8)
|
|
142
|
+
(0, ui_1.warn)("password is short (< 8 chars). consider a stronger password.");
|
|
143
|
+
pwSource = "inline";
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
password = _NO_PW;
|
|
147
|
+
pwSource = "none";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
password = (0, password_1.resolvePassword)(rawPw);
|
|
152
|
+
pwSource = (0, password_1.describePasswordSource)(rawPw);
|
|
153
|
+
if (password.length < 8)
|
|
154
|
+
(0, ui_1.warn)("password is short (< 8 chars). consider a stronger password.");
|
|
155
|
+
}
|
|
156
|
+
const useV2 = (0, args_1.hasFlag)(parsed.flags, "v2");
|
|
157
|
+
const useV3 = (0, args_1.hasFlag)(parsed.flags, "v3");
|
|
158
|
+
const version = useV3 ? 3 : useV2 ? 2 : 2;
|
|
159
|
+
const compact = (0, args_1.hasFlag)(parsed.flags, "compact");
|
|
160
|
+
let text;
|
|
161
|
+
let source;
|
|
162
|
+
let bytes;
|
|
163
|
+
let encoding;
|
|
164
|
+
const rawInput = parsed.positional[0];
|
|
165
|
+
if (!rawInput) {
|
|
166
|
+
text = await _interactive();
|
|
167
|
+
source = "interactive";
|
|
168
|
+
bytes = Buffer.byteLength(text, "utf8");
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
const r = _readInput(rawInput);
|
|
172
|
+
text = r.text;
|
|
173
|
+
source = r.source;
|
|
174
|
+
bytes = r.bytes;
|
|
175
|
+
encoding = r.encoding;
|
|
176
|
+
}
|
|
177
|
+
if (encoding && encoding !== "utf8") {
|
|
178
|
+
(0, ui_1.warn)(`file encoding detected: ${encoding} — converted to UTF-8 before encrypting`);
|
|
179
|
+
}
|
|
180
|
+
const mb = bytes / 1024 / 1024;
|
|
181
|
+
if (bytes > _WARN) {
|
|
182
|
+
let msg = `\n input is ${mb.toFixed(1)} MB. may be slow.\n`;
|
|
183
|
+
if (env_1.env.isTermux)
|
|
184
|
+
msg += " warning: files above 500 MB may crash Termux.\n";
|
|
185
|
+
msg += " continue? [y/N] ";
|
|
186
|
+
if (!await _confirm(msg)) {
|
|
187
|
+
process.stderr.write("aborted.\n");
|
|
188
|
+
process.exit(0);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const rawExp = (0, args_1.getFlag)(parsed.flags, "expires");
|
|
192
|
+
const expiresMs = rawExp ? (0, crypto_1.parseExpiry)(rawExp) : undefined;
|
|
193
|
+
const useClip = (0, args_1.hasFlag)(parsed.flags, "clip") || !!rc.clip;
|
|
194
|
+
const sizeLabel = mb < 0.01 ? `${bytes} B` : `${mb.toFixed(2)} MB`;
|
|
195
|
+
const t0 = Date.now();
|
|
196
|
+
let tok;
|
|
197
|
+
try {
|
|
198
|
+
tok = (0, crypto_1.encrypt)(text, password, { expiresMs }, version, compact);
|
|
199
|
+
}
|
|
200
|
+
catch (ex) {
|
|
201
|
+
(0, ui_1.err)(ex.message, 2);
|
|
202
|
+
}
|
|
203
|
+
const elapsed = Date.now() - t0;
|
|
204
|
+
(0, history_1.recordHistory)("enc", tok);
|
|
205
|
+
(0, ui_1.section)("encrypt");
|
|
206
|
+
(0, ui_1.box)([
|
|
207
|
+
{ label: "input", value: source === "interactive" ? "(hidden)" : source === "arg" ? (text.length > 52 ? text.slice(0, 52) + "…" : text) : source },
|
|
208
|
+
{ label: "size", value: sizeLabel },
|
|
209
|
+
{ label: "encoding", value: encoding ?? "utf-8" },
|
|
210
|
+
{ label: "pw", value: pwSource },
|
|
211
|
+
{ label: "algo", value: _ALGO_LABEL[version] },
|
|
212
|
+
...(compact ? [{ label: "mode", value: "compact (sec: prefix)" }] : []),
|
|
213
|
+
...(expiresMs ? [{ label: "expires", value: new Date(expiresMs).toISOString() }] : []),
|
|
214
|
+
]);
|
|
215
|
+
(0, ui_1.token)(tok);
|
|
216
|
+
(0, ui_1.ms)(elapsed);
|
|
217
|
+
if (useClip) {
|
|
218
|
+
const copied = (0, clip_1.copyToClipboard)(tok);
|
|
219
|
+
if (copied)
|
|
220
|
+
(0, ui_1.info)("copied to clipboard");
|
|
221
|
+
else
|
|
222
|
+
(0, ui_1.warn)("--clip: no clipboard tool found");
|
|
223
|
+
}
|
|
224
|
+
process.stdout.write(tok + "\n");
|
|
225
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cmdExample(): void;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cmdExample = cmdExample;
|
|
4
|
+
const ui_1 = require("../lib/ui");
|
|
5
|
+
const _NC = process.env.NO_COLOR !== undefined;
|
|
6
|
+
const _c = _NC ? {
|
|
7
|
+
r: "", dim: "", bold: "", hi: "", cyan: "", gray: ""
|
|
8
|
+
} : {
|
|
9
|
+
r: "\x1b[0m",
|
|
10
|
+
dim: "\x1b[2m",
|
|
11
|
+
bold: "\x1b[1m",
|
|
12
|
+
hi: "\x1b[38;5;252m",
|
|
13
|
+
cyan: "\x1b[36m",
|
|
14
|
+
gray: "\x1b[38;5;245m",
|
|
15
|
+
};
|
|
16
|
+
function _ui(s) { process.stderr.write(s); }
|
|
17
|
+
function _visLen(s) {
|
|
18
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
19
|
+
}
|
|
20
|
+
function _codeBox(title, lines) {
|
|
21
|
+
const lw = Math.max(...lines.map(([l]) => l.length));
|
|
22
|
+
const rows = lines.map(([label, val]) => {
|
|
23
|
+
const pad = " ".repeat(lw - label.length + 1);
|
|
24
|
+
const lc = label.startsWith("#") || label === "" ? _c.gray : _c.cyan;
|
|
25
|
+
const vc = label.startsWith("#") || label === "" ? _c.dim : _c.hi;
|
|
26
|
+
return `${lc}${label}${_c.r}${pad}${vc}${val}${_c.r}`;
|
|
27
|
+
});
|
|
28
|
+
const width = Math.max(...rows.map(_visLen), title.length + 4) + 4;
|
|
29
|
+
const hline = (l, r) => `${_c.gray}${l}${"─".repeat(width - 2)}${r}${_c.r}\n`;
|
|
30
|
+
_ui(`\n ${_c.dim}${title}${_c.r}\n`);
|
|
31
|
+
_ui(hline("┌", "┐"));
|
|
32
|
+
for (const row of rows) {
|
|
33
|
+
const pad = Math.max(0, width - _visLen(row) - 2);
|
|
34
|
+
_ui(`${_c.gray}│${_c.r} ${row}${" ".repeat(pad)} ${_c.gray}│${_c.r}\n`);
|
|
35
|
+
}
|
|
36
|
+
_ui(hline("└", "┘"));
|
|
37
|
+
}
|
|
38
|
+
function cmdExample() {
|
|
39
|
+
_ui(`\n${_c.gray}${"─".repeat(48)}${_c.r}\n`);
|
|
40
|
+
_ui(` ${_c.bold}${_c.cyan}${ui_1.PKG}${_c.r} ${_c.gray}v${ui_1.VERSION} examples${_c.r}\n`);
|
|
41
|
+
_ui(`${_c.gray}${"─".repeat(48)}${_c.r}\n`);
|
|
42
|
+
_codeBox("cli", [
|
|
43
|
+
["# encrypt", ""],
|
|
44
|
+
[`${ui_1.BIN} enc`, `'my secret' -sw mypassword`],
|
|
45
|
+
[`${ui_1.BIN} enc`, `'expires in 1h' -sw mypassword --expires 1h`],
|
|
46
|
+
[`${ui_1.BIN} enc`, `@notes.txt -sw mypassword`],
|
|
47
|
+
["", ""],
|
|
48
|
+
["# decrypt", ""],
|
|
49
|
+
[`${ui_1.BIN} denc`, `'secry:v2:...' -sw mypassword`],
|
|
50
|
+
[`${ui_1.BIN} denc`, `'secry:v2:...' -sw mypassword --out result.txt`],
|
|
51
|
+
["", ""],
|
|
52
|
+
["# generate password", ""],
|
|
53
|
+
[`${ui_1.BIN} gen`, `32`],
|
|
54
|
+
["", ""],
|
|
55
|
+
["# verify", ""],
|
|
56
|
+
[`${ui_1.BIN} verify`, `'secry:v2:...' -sw mypassword`],
|
|
57
|
+
["", ""],
|
|
58
|
+
["# quiet mode (token only)", ""],
|
|
59
|
+
[`${ui_1.BIN} enc`, `'my secret' -sw mypassword --quiet`],
|
|
60
|
+
["", ""],
|
|
61
|
+
["# capture token in variable", ""],
|
|
62
|
+
[`TOKEN=$(${ui_1.BIN} enc`, `'text' -sw mypassword --quiet)`],
|
|
63
|
+
[`${ui_1.BIN} denc`, `"$TOKEN" -sw mypassword`],
|
|
64
|
+
]);
|
|
65
|
+
_codeBox("library (node.js)", [
|
|
66
|
+
["# install", ""],
|
|
67
|
+
["npm install", "secry"],
|
|
68
|
+
["", ""],
|
|
69
|
+
["import secry", "from 'secry'"],
|
|
70
|
+
["", ""],
|
|
71
|
+
["# generate password", ""],
|
|
72
|
+
["const pw", "= secry.generate(32)"],
|
|
73
|
+
["", ""],
|
|
74
|
+
["# encrypt", ""],
|
|
75
|
+
["const token", "= secry.encrypt('my secret', pw)"],
|
|
76
|
+
["const token", "= secry.encrypt('expires', pw, { expiresMs: Date.now() + 3600_000 })"],
|
|
77
|
+
["", ""],
|
|
78
|
+
["# decrypt", ""],
|
|
79
|
+
["const text", "= secry.decrypt(token).key(pw)"],
|
|
80
|
+
["", ""],
|
|
81
|
+
["# verify", ""],
|
|
82
|
+
["const ok", "= secry.verify(token, pw)"],
|
|
83
|
+
["", ""],
|
|
84
|
+
["# fingerprint", ""],
|
|
85
|
+
["const fp", "= secry.fingerprint('hello', 'secret')"],
|
|
86
|
+
["", ""],
|
|
87
|
+
["# inspect token", ""],
|
|
88
|
+
["const meta", "= secry.inspect(token)"],
|
|
89
|
+
["// meta", "{ version, algo, saltHex, nonceHex, payloadBytes }"],
|
|
90
|
+
]);
|
|
91
|
+
_ui(`\n${_c.gray}${"─".repeat(48)}${_c.r}\n\n`);
|
|
92
|
+
}
|
package/dist/cmd/fp.d.ts
ADDED
package/dist/cmd/fp.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cmdFp = cmdFp;
|
|
4
|
+
const crypto_1 = require("../lib/crypto");
|
|
5
|
+
const password_1 = require("../lib/password");
|
|
6
|
+
const ui_1 = require("../lib/ui");
|
|
7
|
+
const rc_1 = require("../lib/rc");
|
|
8
|
+
const args_1 = require("../lib/args");
|
|
9
|
+
function cmdFp(parsed) {
|
|
10
|
+
const rc = (0, rc_1.loadRC)();
|
|
11
|
+
const text = parsed.positional[0];
|
|
12
|
+
if (!text)
|
|
13
|
+
(0, ui_1.err)("missing <text> argument");
|
|
14
|
+
const rawPw = (0, args_1.getFlag)(parsed.flags, "sw") ?? rc.password;
|
|
15
|
+
if (!rawPw)
|
|
16
|
+
(0, ui_1.err)("-sw <secret> is required");
|
|
17
|
+
const secret = (0, password_1.resolvePassword)(rawPw);
|
|
18
|
+
const t0 = Date.now();
|
|
19
|
+
const fp = (0, crypto_1.hmacFingerprint)(text, secret);
|
|
20
|
+
const elapsed = Date.now() - t0;
|
|
21
|
+
(0, ui_1.section)("fingerprint");
|
|
22
|
+
(0, ui_1.box)([
|
|
23
|
+
{ label: "input", value: text.length > 52 ? text.slice(0, 52) + "…" : text },
|
|
24
|
+
{ label: "algo", value: "HMAC-SHA256" },
|
|
25
|
+
{ label: "fp", value: fp },
|
|
26
|
+
]);
|
|
27
|
+
(0, ui_1.ms)(elapsed);
|
|
28
|
+
(0, ui_1.data)(fp);
|
|
29
|
+
}
|
package/dist/cmd/gen.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cmdGen = cmdGen;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const ui_1 = require("../lib/ui");
|
|
6
|
+
const clip_1 = require("../lib/clip");
|
|
7
|
+
const rc_1 = require("../lib/rc");
|
|
8
|
+
const args_1 = require("../lib/args");
|
|
9
|
+
const _B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
10
|
+
function _gen(bytes, upper) {
|
|
11
|
+
const raw = (0, crypto_1.randomBytes)(bytes);
|
|
12
|
+
let pw = "";
|
|
13
|
+
for (const b of raw)
|
|
14
|
+
pw += _B64[b % 64];
|
|
15
|
+
if (upper && !/[A-Z]/.test(pw)) {
|
|
16
|
+
const i = Math.floor(Math.random() * pw.length);
|
|
17
|
+
pw = pw.slice(0, i) + pw[i].toUpperCase() + pw.slice(i + 1);
|
|
18
|
+
}
|
|
19
|
+
return pw;
|
|
20
|
+
}
|
|
21
|
+
function _entropy(bytes) {
|
|
22
|
+
const bits = Math.floor(bytes * 5.954);
|
|
23
|
+
if (bits >= 256)
|
|
24
|
+
return `${bits} bits — extreme`;
|
|
25
|
+
if (bits >= 128)
|
|
26
|
+
return `${bits} bits — very strong`;
|
|
27
|
+
if (bits >= 80)
|
|
28
|
+
return `${bits} bits — strong`;
|
|
29
|
+
if (bits >= 60)
|
|
30
|
+
return `${bits} bits — good`;
|
|
31
|
+
return `${bits} bits — weak`;
|
|
32
|
+
}
|
|
33
|
+
function cmdGen(parsed) {
|
|
34
|
+
const rc = (0, rc_1.loadRC)();
|
|
35
|
+
const raw = parsed.positional[0];
|
|
36
|
+
const bytes = raw ? parseInt(raw, 10) : (rc.bytes ?? 24);
|
|
37
|
+
const useClip = (0, args_1.hasFlag)(parsed.flags, "clip") || !!rc.clip;
|
|
38
|
+
const upper = (0, args_1.hasFlag)(parsed.flags, "upper");
|
|
39
|
+
const rawCount = (0, args_1.getFlag)(parsed.flags, "count");
|
|
40
|
+
const count = rawCount ? parseInt(rawCount, 10) : 1;
|
|
41
|
+
if (isNaN(bytes) || bytes < 8 || bytes > 256)
|
|
42
|
+
(0, ui_1.err)("bytes must be between 8 and 256");
|
|
43
|
+
if (isNaN(count) || count < 1 || count > 20)
|
|
44
|
+
(0, ui_1.err)("--count must be between 1 and 20");
|
|
45
|
+
const t0 = Date.now();
|
|
46
|
+
const passwords = Array.from({ length: count }, () => _gen(bytes, upper));
|
|
47
|
+
const elapsed = Date.now() - t0;
|
|
48
|
+
(0, ui_1.section)("generate password");
|
|
49
|
+
if (count === 1) {
|
|
50
|
+
(0, ui_1.box)([
|
|
51
|
+
{ label: "bytes", value: String(bytes) },
|
|
52
|
+
{ label: "entropy", value: _entropy(bytes) },
|
|
53
|
+
{ label: "upper", value: upper ? "yes" : "no" },
|
|
54
|
+
{ label: "pw", value: passwords[0] },
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
(0, ui_1.box)([
|
|
59
|
+
{ label: "bytes", value: String(bytes) },
|
|
60
|
+
{ label: "entropy", value: _entropy(bytes) },
|
|
61
|
+
{ label: "upper", value: upper ? "yes" : "no" },
|
|
62
|
+
{ label: "count", value: String(count) },
|
|
63
|
+
...passwords.map((pw, i) => ({ label: `pw ${i + 1}`, value: pw })),
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
(0, ui_1.ms)(elapsed);
|
|
67
|
+
if (useClip) {
|
|
68
|
+
const copied = (0, clip_1.copyToClipboard)(passwords[0]);
|
|
69
|
+
if (copied)
|
|
70
|
+
(0, ui_1.info)("copied to clipboard (pw 1)");
|
|
71
|
+
else
|
|
72
|
+
(0, ui_1.warn)("--clip: no clipboard tool found");
|
|
73
|
+
process.stderr.write("\n");
|
|
74
|
+
}
|
|
75
|
+
passwords.forEach((pw) => (0, ui_1.data)(pw));
|
|
76
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cmdHistory = cmdHistory;
|
|
4
|
+
const history_1 = require("../lib/history");
|
|
5
|
+
const ui_1 = require("../lib/ui");
|
|
6
|
+
const args_1 = require("../lib/args");
|
|
7
|
+
function cmdHistory(parsed) {
|
|
8
|
+
if ((0, args_1.hasFlag)(parsed.flags, "clear")) {
|
|
9
|
+
(0, history_1.clearHistory)();
|
|
10
|
+
(0, ui_1.ok)("history cleared");
|
|
11
|
+
process.stderr.write("\n");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const entries = (0, history_1.getHistory)();
|
|
15
|
+
if (entries.length === 0) {
|
|
16
|
+
(0, ui_1.warn)("no history yet\n");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
(0, ui_1.section)("history");
|
|
20
|
+
(0, ui_1.box)(entries.map((e) => ({
|
|
21
|
+
label: new Date(e.ts).toISOString().slice(0, 19).replace("T", " "),
|
|
22
|
+
value: `${e.cmd.padEnd(5)} fp:${e.fp}`,
|
|
23
|
+
})));
|
|
24
|
+
process.stderr.write("\n");
|
|
25
|
+
}
|
package/dist/cmd/info.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cmdInfo = cmdInfo;
|
|
4
|
+
const crypto_1 = require("../lib/crypto");
|
|
5
|
+
const ui_1 = require("../lib/ui");
|
|
6
|
+
function cmdInfo(parsed) {
|
|
7
|
+
const token = parsed.positional[0];
|
|
8
|
+
if (!token)
|
|
9
|
+
(0, ui_1.err)("missing <token> argument");
|
|
10
|
+
let meta;
|
|
11
|
+
try {
|
|
12
|
+
meta = (0, crypto_1.inspectToken)(token);
|
|
13
|
+
}
|
|
14
|
+
catch (ex) {
|
|
15
|
+
(0, ui_1.err)(ex.message, 2);
|
|
16
|
+
}
|
|
17
|
+
(0, ui_1.section)("token info");
|
|
18
|
+
(0, ui_1.box)([
|
|
19
|
+
{ label: "prefix", value: meta.prefix },
|
|
20
|
+
{ label: "version", value: meta.version },
|
|
21
|
+
{ label: "algo", value: meta.algo },
|
|
22
|
+
{ label: "kdf", value: "scrypt (N=16384, r=8, p=1)" },
|
|
23
|
+
{ label: "salt", value: meta.saltHex },
|
|
24
|
+
{ label: "nonce", value: meta.nonceHex },
|
|
25
|
+
{ label: "bytes", value: `${meta.payloadBytes} (raw payload)` },
|
|
26
|
+
]);
|
|
27
|
+
process.stderr.write("\n");
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cmdRoot(): Promise<void>;
|