@certen.io/cli 0.2.0 → 0.3.1
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 +136 -0
- package/dist/commands/identity.js +23 -3
- package/dist/commands/identity.js.map +1 -1
- package/dist/commands/keys.d.ts +9 -0
- package/dist/commands/keys.js +109 -0
- package/dist/commands/keys.js.map +1 -0
- package/dist/commands/pending.js +13 -5
- package/dist/commands/pending.js.map +1 -1
- package/dist/commands/transaction.js +70 -8
- package/dist/commands/transaction.js.map +1 -1
- package/dist/config.js +9 -1
- package/dist/config.js.map +1 -1
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore.d.ts +79 -0
- package/dist/keystore.js +274 -0
- package/dist/keystore.js.map +1 -0
- package/dist/passphrase.d.ts +25 -0
- package/dist/passphrase.js +111 -0
- package/dist/passphrase.js.map +1 -0
- package/dist/signer.d.ts +31 -0
- package/dist/signer.js +36 -0
- package/dist/signer.js.map +1 -0
- package/package.json +56 -56
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
declare const KEYS_DIR: string;
|
|
2
|
+
/**
|
|
3
|
+
* A local Ed25519 signing key.
|
|
4
|
+
*
|
|
5
|
+
* The CLI shipped without one, which made it read-only in practice: `tx sign` takes
|
|
6
|
+
* `--signature <hex>`, so anyone without their own Ed25519 tooling could authenticate and
|
|
7
|
+
* query and then stop. The SDK's `sign` callback covers people writing code; it does nothing
|
|
8
|
+
* for someone driving the CLI.
|
|
9
|
+
*
|
|
10
|
+
* The non-custodial posture is unchanged. The key is generated here, encrypted here, and
|
|
11
|
+
* never leaves this machine — the CLI sends a signature, never a private key. This file is
|
|
12
|
+
* the local half of the same contract the SDK states: you hold the key, we hand you bytes.
|
|
13
|
+
*/
|
|
14
|
+
/** scrypt, not argon2id. */
|
|
15
|
+
declare const KDF: {
|
|
16
|
+
readonly N: 65536;
|
|
17
|
+
readonly r: 8;
|
|
18
|
+
readonly p: 1;
|
|
19
|
+
readonly keylen: 32;
|
|
20
|
+
};
|
|
21
|
+
declare const CIPHER = "aes-256-gcm";
|
|
22
|
+
export interface StoredKey {
|
|
23
|
+
version: number;
|
|
24
|
+
name: string;
|
|
25
|
+
algorithm: 'ed25519';
|
|
26
|
+
/** Raw 32-byte Ed25519 public key, hex. */
|
|
27
|
+
publicKey: string;
|
|
28
|
+
/** sha256(raw public key), hex — what `identity create` wants as `public_key_hash`. */
|
|
29
|
+
publicKeyHash: string;
|
|
30
|
+
createdAt: string;
|
|
31
|
+
encryption: {
|
|
32
|
+
kdf: 'scrypt';
|
|
33
|
+
params: typeof KDF;
|
|
34
|
+
salt: string;
|
|
35
|
+
cipher: typeof CIPHER;
|
|
36
|
+
iv: string;
|
|
37
|
+
authTag: string;
|
|
38
|
+
} | null;
|
|
39
|
+
/** PKCS8 DER, hex. Ciphertext when `encryption` is set, plaintext when it is null. */
|
|
40
|
+
privateKey: string;
|
|
41
|
+
}
|
|
42
|
+
export interface KeyInfo {
|
|
43
|
+
name: string;
|
|
44
|
+
publicKey: string;
|
|
45
|
+
publicKeyHash: string;
|
|
46
|
+
createdAt: string;
|
|
47
|
+
encrypted: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare function assertValidName(name: string): void;
|
|
50
|
+
declare function keyPath(name: string): string;
|
|
51
|
+
export declare function publicKeyHashOf(rawPub: Buffer): string;
|
|
52
|
+
export declare function keyExists(name: string): boolean;
|
|
53
|
+
export declare function generateKey(name: string, passphrase: string | null): KeyInfo;
|
|
54
|
+
export declare function readKeyFile(name: string): StoredKey;
|
|
55
|
+
/** Metadata only — never decrypts, so it never needs a passphrase. */
|
|
56
|
+
export declare function getKeyInfo(name: string): KeyInfo;
|
|
57
|
+
export declare function listKeys(): KeyInfo[];
|
|
58
|
+
export declare function deleteKey(name: string): void;
|
|
59
|
+
export declare function isEncrypted(name: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Sign the RAW BYTES of `hashHex` — matching the SDK's `SignFn` contract exactly.
|
|
62
|
+
*
|
|
63
|
+
* The two ways to get this wrong are signing the ASCII of the hex string, and hashing the hash
|
|
64
|
+
* again before signing. Both produce a well-formed 128-hex signature that the gateway rejects
|
|
65
|
+
* with a signature-verification error that names neither cause, so the decoding happens here
|
|
66
|
+
* once rather than in every caller.
|
|
67
|
+
*/
|
|
68
|
+
export declare function signHash(name: string, passphrase: string | null, hashHex: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Verify a stored key can actually produce a signature its own public key accepts.
|
|
71
|
+
*
|
|
72
|
+
* Cheap, and it catches a passphrase that decrypts to garbage which happens to survive GCM
|
|
73
|
+
* (it cannot, in practice) as well as any future format drift, before a signature goes to the
|
|
74
|
+
* gateway and comes back as an opaque rejection.
|
|
75
|
+
*/
|
|
76
|
+
export declare function selfTest(name: string, passphrase: string | null): boolean;
|
|
77
|
+
/** Constant-time compare for the confirm-passphrase path. */
|
|
78
|
+
export declare function samePassphrase(a: string, b: string): boolean;
|
|
79
|
+
export { KEYS_DIR, keyPath };
|
package/dist/keystore.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, createHash, createPrivateKey, createPublicKey, generateKeyPairSync, randomBytes, scryptSync, sign as edSign, timingSafeEqual, verify as edVerify, } from 'crypto';
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, statSync, readdirSync, unlinkSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
const KEYS_DIR = join(homedir(), '.certen', 'keys');
|
|
6
|
+
/**
|
|
7
|
+
* A local Ed25519 signing key.
|
|
8
|
+
*
|
|
9
|
+
* The CLI shipped without one, which made it read-only in practice: `tx sign` takes
|
|
10
|
+
* `--signature <hex>`, so anyone without their own Ed25519 tooling could authenticate and
|
|
11
|
+
* query and then stop. The SDK's `sign` callback covers people writing code; it does nothing
|
|
12
|
+
* for someone driving the CLI.
|
|
13
|
+
*
|
|
14
|
+
* The non-custodial posture is unchanged. The key is generated here, encrypted here, and
|
|
15
|
+
* never leaves this machine — the CLI sends a signature, never a private key. This file is
|
|
16
|
+
* the local half of the same contract the SDK states: you hold the key, we hand you bytes.
|
|
17
|
+
*/
|
|
18
|
+
/** scrypt, not argon2id. */
|
|
19
|
+
// argon2id is the better KDF and was the original intent. Every Node implementation of it is a
|
|
20
|
+
// native module, and a native dependency in a globally-installed CLI turns `npm i -g @certen.io/cli`
|
|
21
|
+
// into a compiler invocation that fails on any machine without build tools — the exact audience
|
|
22
|
+
// this command exists to serve. scrypt is memory-hard, in the standard library, and at these
|
|
23
|
+
// parameters (~134 MB, ~1s) is a sound choice for a passphrase-derived file key. Revisit if a
|
|
24
|
+
// pure-WASM argon2id becomes viable.
|
|
25
|
+
const KDF = { N: 65536, r: 8, p: 1, keylen: 32 };
|
|
26
|
+
// Node's scrypt refuses to allocate past `maxmem` (32 MB default). 128*N*r is ~67 MB here, so it
|
|
27
|
+
// must be raised explicitly or every generate fails with an opaque ERR_CRYPTO_INVALID_SCRYPT_PARAM.
|
|
28
|
+
const SCRYPT_MAXMEM = 256 * 1024 * 1024;
|
|
29
|
+
const CIPHER = 'aes-256-gcm';
|
|
30
|
+
const VERSION = 1;
|
|
31
|
+
/**
|
|
32
|
+
* Key names become filenames, so anything that could escape the directory or collide with a
|
|
33
|
+
* different name after normalization is rejected rather than sanitized. Silently rewriting a
|
|
34
|
+
* name means `certen keys sign --name ../../x` signs with something the user did not choose.
|
|
35
|
+
*/
|
|
36
|
+
const NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
37
|
+
export function assertValidName(name) {
|
|
38
|
+
if (!NAME_RE.test(name)) {
|
|
39
|
+
throw new Error(`Invalid key name "${name}". Use 1-64 characters: letters, digits, hyphen, underscore; must start with a letter or digit.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function keyPath(name) {
|
|
43
|
+
assertValidName(name);
|
|
44
|
+
return join(KEYS_DIR, `${name}.json`);
|
|
45
|
+
}
|
|
46
|
+
function ensureKeysDir() {
|
|
47
|
+
if (!existsSync(KEYS_DIR)) {
|
|
48
|
+
mkdirSync(KEYS_DIR, { recursive: true, mode: 0o700 });
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
chmodSync(KEYS_DIR, 0o700);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Windows or a non-permission-aware filesystem: best effort, same as config.ts.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function writeKeyFile(name, key) {
|
|
58
|
+
ensureKeysDir();
|
|
59
|
+
const path = keyPath(name);
|
|
60
|
+
writeFileSync(path, JSON.stringify(key, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
61
|
+
try {
|
|
62
|
+
chmodSync(path, 0o600);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Best effort, as above.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Refuse to read a key file that group or world can read. Same rule config.ts applies to the
|
|
70
|
+
* API key, and it matters more here: an API key can be revoked from the portal in one click,
|
|
71
|
+
* a signing key that controls an on-chain key page cannot.
|
|
72
|
+
*
|
|
73
|
+
* POSIX only. Windows does not implement POSIX mode bits — `chmod` is a no-op there and
|
|
74
|
+
* `statSync().mode` reports a synthesized 0666/0444 derived from the read-only attribute, never
|
|
75
|
+
* 0600. Enforcing the check on that synthetic value rejects every key file on Windows, including
|
|
76
|
+
* ones the user just created, while telling them to run a `chmod` that cannot fix it. Access
|
|
77
|
+
* control on Windows is the file's ACL, which it inherits from the user profile directory.
|
|
78
|
+
*/
|
|
79
|
+
function assertFileSecure(path) {
|
|
80
|
+
if (process.platform === 'win32')
|
|
81
|
+
return;
|
|
82
|
+
try {
|
|
83
|
+
const mode = statSync(path).mode & 0o077;
|
|
84
|
+
if (mode !== 0) {
|
|
85
|
+
throw new Error(`Refusing to read ${path} — permissions are not 0600. Run: chmod 600 "${path}"`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
if (err instanceof Error && err.message.startsWith('Refusing'))
|
|
90
|
+
throw err;
|
|
91
|
+
// stat failed for a reason other than our own check (missing file): let the caller's
|
|
92
|
+
// existence check produce the clearer message.
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Raw 32-byte public key from a KeyObject. The last 32 bytes of the SPKI DER are the key. */
|
|
96
|
+
function rawPublicKey(publicKey) {
|
|
97
|
+
const spki = publicKey.export({ format: 'der', type: 'spki' });
|
|
98
|
+
return spki.subarray(spki.length - 32);
|
|
99
|
+
}
|
|
100
|
+
export function publicKeyHashOf(rawPub) {
|
|
101
|
+
return createHash('sha256').update(rawPub).digest('hex');
|
|
102
|
+
}
|
|
103
|
+
function deriveFileKey(passphrase, salt) {
|
|
104
|
+
return scryptSync(passphrase, salt, KDF.keylen, {
|
|
105
|
+
N: KDF.N, r: KDF.r, p: KDF.p, maxmem: SCRYPT_MAXMEM,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
export function keyExists(name) {
|
|
109
|
+
return existsSync(keyPath(name));
|
|
110
|
+
}
|
|
111
|
+
export function generateKey(name, passphrase) {
|
|
112
|
+
assertValidName(name);
|
|
113
|
+
if (keyExists(name)) {
|
|
114
|
+
throw new Error(`Key "${name}" already exists at ${keyPath(name)}. Choose another name or delete it first.`);
|
|
115
|
+
}
|
|
116
|
+
const { publicKey, privateKey } = generateKeyPairSync('ed25519');
|
|
117
|
+
const rawPub = rawPublicKey(publicKey);
|
|
118
|
+
const pkcs8 = privateKey.export({ format: 'der', type: 'pkcs8' });
|
|
119
|
+
let encryption = null;
|
|
120
|
+
let stored;
|
|
121
|
+
if (passphrase === null) {
|
|
122
|
+
stored = pkcs8.toString('hex');
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
const salt = randomBytes(16);
|
|
126
|
+
const iv = randomBytes(12);
|
|
127
|
+
const fileKey = deriveFileKey(passphrase, salt);
|
|
128
|
+
const cipher = createCipheriv(CIPHER, fileKey, iv);
|
|
129
|
+
const ct = Buffer.concat([cipher.update(pkcs8), cipher.final()]);
|
|
130
|
+
encryption = {
|
|
131
|
+
kdf: 'scrypt', params: KDF, salt: salt.toString('hex'),
|
|
132
|
+
cipher: CIPHER, iv: iv.toString('hex'), authTag: cipher.getAuthTag().toString('hex'),
|
|
133
|
+
};
|
|
134
|
+
stored = ct.toString('hex');
|
|
135
|
+
}
|
|
136
|
+
const key = {
|
|
137
|
+
version: VERSION,
|
|
138
|
+
name,
|
|
139
|
+
algorithm: 'ed25519',
|
|
140
|
+
publicKey: rawPub.toString('hex'),
|
|
141
|
+
publicKeyHash: publicKeyHashOf(rawPub),
|
|
142
|
+
createdAt: new Date().toISOString(),
|
|
143
|
+
encryption,
|
|
144
|
+
privateKey: stored,
|
|
145
|
+
};
|
|
146
|
+
writeKeyFile(name, key);
|
|
147
|
+
return toInfo(key);
|
|
148
|
+
}
|
|
149
|
+
function toInfo(k) {
|
|
150
|
+
return {
|
|
151
|
+
name: k.name,
|
|
152
|
+
publicKey: k.publicKey,
|
|
153
|
+
publicKeyHash: k.publicKeyHash,
|
|
154
|
+
createdAt: k.createdAt,
|
|
155
|
+
encrypted: k.encryption !== null,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export function readKeyFile(name) {
|
|
159
|
+
const path = keyPath(name);
|
|
160
|
+
if (!existsSync(path)) {
|
|
161
|
+
throw new Error(`No key named "${name}". Run \`certen keys list\` to see what you have, or \`certen keys generate --name ${name}\`.`);
|
|
162
|
+
}
|
|
163
|
+
assertFileSecure(path);
|
|
164
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
165
|
+
if (parsed.version !== VERSION) {
|
|
166
|
+
throw new Error(`Key "${name}" has unsupported version ${parsed.version} (this CLI understands ${VERSION}).`);
|
|
167
|
+
}
|
|
168
|
+
if (parsed.algorithm !== 'ed25519') {
|
|
169
|
+
throw new Error(`Key "${name}" uses unsupported algorithm "${parsed.algorithm}".`);
|
|
170
|
+
}
|
|
171
|
+
return parsed;
|
|
172
|
+
}
|
|
173
|
+
/** Metadata only — never decrypts, so it never needs a passphrase. */
|
|
174
|
+
export function getKeyInfo(name) {
|
|
175
|
+
return toInfo(readKeyFile(name));
|
|
176
|
+
}
|
|
177
|
+
export function listKeys() {
|
|
178
|
+
if (!existsSync(KEYS_DIR))
|
|
179
|
+
return [];
|
|
180
|
+
return readdirSync(KEYS_DIR)
|
|
181
|
+
.filter((f) => f.endsWith('.json'))
|
|
182
|
+
.map((f) => f.slice(0, -5))
|
|
183
|
+
.filter((n) => NAME_RE.test(n))
|
|
184
|
+
.map((n) => {
|
|
185
|
+
try {
|
|
186
|
+
return toInfo(readKeyFile(n));
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// A corrupt or unreadable file should not make `keys list` fail for every other key.
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
.filter((k) => k !== null)
|
|
194
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
195
|
+
}
|
|
196
|
+
export function deleteKey(name) {
|
|
197
|
+
const path = keyPath(name);
|
|
198
|
+
if (!existsSync(path))
|
|
199
|
+
throw new Error(`No key named "${name}".`);
|
|
200
|
+
unlinkSync(path);
|
|
201
|
+
}
|
|
202
|
+
export function isEncrypted(name) {
|
|
203
|
+
return readKeyFile(name).encryption !== null;
|
|
204
|
+
}
|
|
205
|
+
function decryptPrivateKey(k, passphrase) {
|
|
206
|
+
if (k.encryption === null)
|
|
207
|
+
return Buffer.from(k.privateKey, 'hex');
|
|
208
|
+
if (passphrase === null) {
|
|
209
|
+
throw new Error(`Key "${k.name}" is encrypted — a passphrase is required.`);
|
|
210
|
+
}
|
|
211
|
+
const enc = k.encryption;
|
|
212
|
+
const fileKey = deriveFileKey(passphrase, Buffer.from(enc.salt, 'hex'));
|
|
213
|
+
const decipher = createDecipheriv(enc.cipher, fileKey, Buffer.from(enc.iv, 'hex'));
|
|
214
|
+
decipher.setAuthTag(Buffer.from(enc.authTag, 'hex'));
|
|
215
|
+
try {
|
|
216
|
+
return Buffer.concat([decipher.update(Buffer.from(k.privateKey, 'hex')), decipher.final()]);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// GCM auth failure is indistinguishable from a wrong passphrase, and saying "wrong
|
|
220
|
+
// passphrase" for a corrupted file sends people down the wrong path. Say both.
|
|
221
|
+
throw new Error(`Could not decrypt key "${k.name}" — wrong passphrase, or the file has been modified.`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Sign the RAW BYTES of `hashHex` — matching the SDK's `SignFn` contract exactly.
|
|
226
|
+
*
|
|
227
|
+
* The two ways to get this wrong are signing the ASCII of the hex string, and hashing the hash
|
|
228
|
+
* again before signing. Both produce a well-formed 128-hex signature that the gateway rejects
|
|
229
|
+
* with a signature-verification error that names neither cause, so the decoding happens here
|
|
230
|
+
* once rather than in every caller.
|
|
231
|
+
*/
|
|
232
|
+
export function signHash(name, passphrase, hashHex) {
|
|
233
|
+
const clean = hashHex.trim().toLowerCase().replace(/^0x/, '');
|
|
234
|
+
if (!/^[0-9a-f]+$/.test(clean) || clean.length % 2 !== 0) {
|
|
235
|
+
throw new Error(`--hash must be an even-length hex string, got "${hashHex}".`);
|
|
236
|
+
}
|
|
237
|
+
const k = readKeyFile(name);
|
|
238
|
+
const pkcs8 = decryptPrivateKey(k, passphrase);
|
|
239
|
+
const privateKey = createPrivateKey({ key: pkcs8, format: 'der', type: 'pkcs8' });
|
|
240
|
+
const sig = edSign(null, Buffer.from(clean, 'hex'), privateKey);
|
|
241
|
+
return sig.toString('hex');
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Verify a stored key can actually produce a signature its own public key accepts.
|
|
245
|
+
*
|
|
246
|
+
* Cheap, and it catches a passphrase that decrypts to garbage which happens to survive GCM
|
|
247
|
+
* (it cannot, in practice) as well as any future format drift, before a signature goes to the
|
|
248
|
+
* gateway and comes back as an opaque rejection.
|
|
249
|
+
*/
|
|
250
|
+
export function selfTest(name, passphrase) {
|
|
251
|
+
const k = readKeyFile(name);
|
|
252
|
+
const probe = randomBytes(32).toString('hex');
|
|
253
|
+
const sig = Buffer.from(signHash(name, passphrase, probe), 'hex');
|
|
254
|
+
const pub = createPublicKey({
|
|
255
|
+
// SPKI DER prefix for Ed25519, then the raw 32-byte key.
|
|
256
|
+
key: Buffer.concat([
|
|
257
|
+
Buffer.from('302a300506032b6570032100', 'hex'),
|
|
258
|
+
Buffer.from(k.publicKey, 'hex'),
|
|
259
|
+
]),
|
|
260
|
+
format: 'der',
|
|
261
|
+
type: 'spki',
|
|
262
|
+
});
|
|
263
|
+
return edVerify(null, Buffer.from(probe, 'hex'), pub, sig);
|
|
264
|
+
}
|
|
265
|
+
/** Constant-time compare for the confirm-passphrase path. */
|
|
266
|
+
export function samePassphrase(a, b) {
|
|
267
|
+
const ab = Buffer.from(a, 'utf-8');
|
|
268
|
+
const bb = Buffer.from(b, 'utf-8');
|
|
269
|
+
if (ab.length !== bb.length)
|
|
270
|
+
return false;
|
|
271
|
+
return timingSafeEqual(ab, bb);
|
|
272
|
+
}
|
|
273
|
+
export { KEYS_DIR, keyPath };
|
|
274
|
+
//# sourceMappingURL=keystore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keystore.js","sourceRoot":"","sources":["../src/keystore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Passphrase resolution for local signing keys.
|
|
3
|
+
*
|
|
4
|
+
* Order is deliberate: an explicit env var always wins so CI and scripted runs never depend on a
|
|
5
|
+
* TTY, and the interactive prompt is the fallback rather than the default. A command that blocks
|
|
6
|
+
* on a hidden prompt inside a pipeline looks like a hang, which is the worst failure mode here.
|
|
7
|
+
*/
|
|
8
|
+
declare const ENV_VAR = "CERTEN_KEY_PASSPHRASE";
|
|
9
|
+
export declare function passphraseFromEnv(): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the passphrase for reading an existing key.
|
|
12
|
+
*
|
|
13
|
+
* `encrypted === false` short-circuits so an unencrypted key never prompts — otherwise every
|
|
14
|
+
* `--no-passphrase` key would still stop and ask for one it does not use.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolvePassphrase(encrypted: boolean, keyName: string): Promise<string | null>;
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the passphrase for a NEW key, confirming it.
|
|
19
|
+
*
|
|
20
|
+
* A typo here is unrecoverable — there is no reset for a key that only exists locally — so the
|
|
21
|
+
* confirmation is not optional when prompting. Supplying it via the env var skips confirmation,
|
|
22
|
+
* because a script that set it once cannot mistype it twice differently.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveNewPassphrase(noPassphrase: boolean): Promise<string | null>;
|
|
25
|
+
export { ENV_VAR as PASSPHRASE_ENV_VAR };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Passphrase resolution for local signing keys.
|
|
3
|
+
*
|
|
4
|
+
* Order is deliberate: an explicit env var always wins so CI and scripted runs never depend on a
|
|
5
|
+
* TTY, and the interactive prompt is the fallback rather than the default. A command that blocks
|
|
6
|
+
* on a hidden prompt inside a pipeline looks like a hang, which is the worst failure mode here.
|
|
7
|
+
*/
|
|
8
|
+
const ENV_VAR = 'CERTEN_KEY_PASSPHRASE';
|
|
9
|
+
export function passphraseFromEnv() {
|
|
10
|
+
const v = process.env[ENV_VAR];
|
|
11
|
+
return v && v.length > 0 ? v : null;
|
|
12
|
+
}
|
|
13
|
+
function isInteractive() {
|
|
14
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Read a line from the TTY without echoing it.
|
|
18
|
+
*
|
|
19
|
+
* Written against raw stdin rather than readline's historical `output: null` trick, which echoes
|
|
20
|
+
* on some Windows terminals — and a passphrase echoed into scrollback is worse than no prompt.
|
|
21
|
+
*/
|
|
22
|
+
function readHidden(prompt) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const stdin = process.stdin;
|
|
25
|
+
process.stdout.write(prompt);
|
|
26
|
+
const chars = [];
|
|
27
|
+
const wasRaw = stdin.isRaw === true;
|
|
28
|
+
const cleanup = () => {
|
|
29
|
+
stdin.removeListener('data', onData);
|
|
30
|
+
if (stdin.setRawMode)
|
|
31
|
+
stdin.setRawMode(wasRaw);
|
|
32
|
+
stdin.pause();
|
|
33
|
+
process.stdout.write('\n');
|
|
34
|
+
};
|
|
35
|
+
const onData = (chunk) => {
|
|
36
|
+
for (const byte of chunk) {
|
|
37
|
+
switch (byte) {
|
|
38
|
+
case 0x03: // Ctrl-C
|
|
39
|
+
cleanup();
|
|
40
|
+
reject(new Error('Cancelled.'));
|
|
41
|
+
return;
|
|
42
|
+
case 0x0d: // CR
|
|
43
|
+
case 0x0a: // LF
|
|
44
|
+
cleanup();
|
|
45
|
+
resolve(chars.join(''));
|
|
46
|
+
return;
|
|
47
|
+
case 0x7f: // DEL
|
|
48
|
+
case 0x08: // BS
|
|
49
|
+
chars.pop();
|
|
50
|
+
break;
|
|
51
|
+
default:
|
|
52
|
+
// Ignore other control characters; accept everything else as passphrase content.
|
|
53
|
+
if (byte >= 0x20)
|
|
54
|
+
chars.push(String.fromCharCode(byte));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
if (stdin.setRawMode)
|
|
59
|
+
stdin.setRawMode(true);
|
|
60
|
+
stdin.resume();
|
|
61
|
+
stdin.setEncoding('binary');
|
|
62
|
+
stdin.on('data', onData);
|
|
63
|
+
stdin.on('error', (err) => {
|
|
64
|
+
cleanup();
|
|
65
|
+
reject(err);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the passphrase for reading an existing key.
|
|
71
|
+
*
|
|
72
|
+
* `encrypted === false` short-circuits so an unencrypted key never prompts — otherwise every
|
|
73
|
+
* `--no-passphrase` key would still stop and ask for one it does not use.
|
|
74
|
+
*/
|
|
75
|
+
export async function resolvePassphrase(encrypted, keyName) {
|
|
76
|
+
if (!encrypted)
|
|
77
|
+
return null;
|
|
78
|
+
const fromEnv = passphraseFromEnv();
|
|
79
|
+
if (fromEnv !== null)
|
|
80
|
+
return fromEnv;
|
|
81
|
+
if (!isInteractive()) {
|
|
82
|
+
throw new Error(`Key "${keyName}" is encrypted and there is no TTY to prompt on. Set ${ENV_VAR}, or run this interactively.`);
|
|
83
|
+
}
|
|
84
|
+
return readHidden(`Passphrase for key "${keyName}": `);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolve the passphrase for a NEW key, confirming it.
|
|
88
|
+
*
|
|
89
|
+
* A typo here is unrecoverable — there is no reset for a key that only exists locally — so the
|
|
90
|
+
* confirmation is not optional when prompting. Supplying it via the env var skips confirmation,
|
|
91
|
+
* because a script that set it once cannot mistype it twice differently.
|
|
92
|
+
*/
|
|
93
|
+
export async function resolveNewPassphrase(noPassphrase) {
|
|
94
|
+
if (noPassphrase)
|
|
95
|
+
return null;
|
|
96
|
+
const fromEnv = passphraseFromEnv();
|
|
97
|
+
if (fromEnv !== null)
|
|
98
|
+
return fromEnv;
|
|
99
|
+
if (!isInteractive()) {
|
|
100
|
+
throw new Error(`No TTY to prompt for a passphrase. Set ${ENV_VAR}, or pass --no-passphrase to store the key unencrypted.`);
|
|
101
|
+
}
|
|
102
|
+
const first = await readHidden('Passphrase for the new key (empty to store unencrypted): ');
|
|
103
|
+
if (first.length === 0)
|
|
104
|
+
return null;
|
|
105
|
+
const second = await readHidden('Confirm passphrase: ');
|
|
106
|
+
if (first !== second)
|
|
107
|
+
throw new Error('Passphrases did not match. Nothing was written.');
|
|
108
|
+
return first;
|
|
109
|
+
}
|
|
110
|
+
export { ENV_VAR as PASSPHRASE_ENV_VAR };
|
|
111
|
+
//# sourceMappingURL=passphrase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"passphrase.js","sourceRoot":"","sources":["../src/passphrase.ts"],"names":[],"mappings":"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"}
|
package/dist/signer.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge a local key into the shape the rest of the CLI needs.
|
|
3
|
+
*
|
|
4
|
+
* `--sign-with` resolves a stored key once, unlocks it once, and hands back both the public
|
|
5
|
+
* material and a `sign` function. Unlocking once matters: a flow that signs twice must not
|
|
6
|
+
* prompt twice, and holding the passphrase in a closure keeps it off argv where every process
|
|
7
|
+
* lister on the machine can read it.
|
|
8
|
+
*/
|
|
9
|
+
export interface ResolvedSigner {
|
|
10
|
+
publicKey: string;
|
|
11
|
+
publicKeyHash: string;
|
|
12
|
+
sign: (hashHex: string) => string;
|
|
13
|
+
}
|
|
14
|
+
export declare function resolveSigner(name: string): Promise<ResolvedSigner>;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the signature/public-key pair for a command that accepts either `--sign-with` or the
|
|
17
|
+
* explicit `--signature`/`--public-key` pair.
|
|
18
|
+
*
|
|
19
|
+
* The explicit pair stays first-class — it is how an HSM, an air-gapped machine, or someone
|
|
20
|
+
* else's policy signer participates, and removing it would trade one locked-out audience for
|
|
21
|
+
* another. `--sign-with` is the convenience path, not the replacement.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveSignature(opts: {
|
|
24
|
+
signWith?: string;
|
|
25
|
+
signature?: string;
|
|
26
|
+
publicKey?: string;
|
|
27
|
+
hash?: string;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
signature: string;
|
|
30
|
+
publicKey: string;
|
|
31
|
+
}>;
|
package/dist/signer.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getKeyInfo, signHash } from './keystore.js';
|
|
2
|
+
import { resolvePassphrase } from './passphrase.js';
|
|
3
|
+
export async function resolveSigner(name) {
|
|
4
|
+
const info = getKeyInfo(name);
|
|
5
|
+
const passphrase = await resolvePassphrase(info.encrypted, name);
|
|
6
|
+
return {
|
|
7
|
+
publicKey: info.publicKey,
|
|
8
|
+
publicKeyHash: info.publicKeyHash,
|
|
9
|
+
sign: (hashHex) => signHash(name, passphrase, hashHex),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the signature/public-key pair for a command that accepts either `--sign-with` or the
|
|
14
|
+
* explicit `--signature`/`--public-key` pair.
|
|
15
|
+
*
|
|
16
|
+
* The explicit pair stays first-class — it is how an HSM, an air-gapped machine, or someone
|
|
17
|
+
* else's policy signer participates, and removing it would trade one locked-out audience for
|
|
18
|
+
* another. `--sign-with` is the convenience path, not the replacement.
|
|
19
|
+
*/
|
|
20
|
+
export async function resolveSignature(opts) {
|
|
21
|
+
if (opts.signWith) {
|
|
22
|
+
if (opts.signature) {
|
|
23
|
+
throw new Error('Pass either --sign-with or --signature, not both.');
|
|
24
|
+
}
|
|
25
|
+
if (!opts.hash) {
|
|
26
|
+
throw new Error('--sign-with needs the hash to sign; this command did not supply one.');
|
|
27
|
+
}
|
|
28
|
+
const signer = await resolveSigner(opts.signWith);
|
|
29
|
+
return { signature: signer.sign(opts.hash), publicKey: signer.publicKey };
|
|
30
|
+
}
|
|
31
|
+
if (!opts.signature || !opts.publicKey) {
|
|
32
|
+
throw new Error('Provide --sign-with <key>, or both --signature <hex> and --public-key <hex>.');
|
|
33
|
+
}
|
|
34
|
+
return { signature: opts.signature, publicKey: opts.publicKey };
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=signer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.js","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAgBpD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY;IAC9C,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,UAAU,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACjE,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,IAAI,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC;KAC/D,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAKtC;IACC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;QAC1F,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAClD,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;IAC5E,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAC;IAClG,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;AAClE,CAAC"}
|