@push.rocks/smartconfig 6.1.1 → 6.2.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/.smartconfig.json +11 -6
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.appdata.js +15 -58
- package/dist_ts/sshagent/index.d.ts +1 -0
- package/dist_ts/sshagent/index.js +2 -0
- package/dist_ts/sshagent/smartconfig.sshagentsecret.d.ts +62 -0
- package/dist_ts/sshagent/smartconfig.sshagentsecret.js +453 -0
- package/package.json +25 -11
- package/readme.md +69 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.appdata.ts +14 -62
- package/ts/sshagent/index.ts +1 -0
- package/ts/sshagent/smartconfig.sshagentsecret.ts +602 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, createHash, createPublicKey, hkdfSync, randomBytes, verify, } from 'node:crypto';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import * as net from 'node:net';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
const sshAgentFailure = 5;
|
|
7
|
+
const sshAgentRequestIdentities = 11;
|
|
8
|
+
const sshAgentIdentitiesAnswer = 12;
|
|
9
|
+
const sshAgentSignRequest = 13;
|
|
10
|
+
const sshAgentSignResponse = 14;
|
|
11
|
+
const maxAgentResponseBytes = 16 * 1024 * 1024;
|
|
12
|
+
const maxAgentIdentities = 1024;
|
|
13
|
+
const defaultAgentTimeoutMs = 15_000;
|
|
14
|
+
const secretFormat = 'ssh-agent-sign-v1';
|
|
15
|
+
const aadEncoding = 'utf8';
|
|
16
|
+
class BinaryReader {
|
|
17
|
+
constructor(buffer) {
|
|
18
|
+
this.buffer = buffer;
|
|
19
|
+
this.offset = 0;
|
|
20
|
+
}
|
|
21
|
+
readUInt8() {
|
|
22
|
+
this.ensureAvailable(1);
|
|
23
|
+
const value = this.buffer.readUInt8(this.offset);
|
|
24
|
+
this.offset += 1;
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
readUInt32BE() {
|
|
28
|
+
this.ensureAvailable(4);
|
|
29
|
+
const value = this.buffer.readUInt32BE(this.offset);
|
|
30
|
+
this.offset += 4;
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
readBuffer(lengthArg) {
|
|
34
|
+
this.ensureAvailable(lengthArg);
|
|
35
|
+
const value = this.buffer.subarray(this.offset, this.offset + lengthArg);
|
|
36
|
+
this.offset += lengthArg;
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
readSshStringBuffer() {
|
|
40
|
+
return this.readBuffer(this.readUInt32BE());
|
|
41
|
+
}
|
|
42
|
+
readSshStringUtf8() {
|
|
43
|
+
return this.readSshStringBuffer().toString('utf8');
|
|
44
|
+
}
|
|
45
|
+
get remainingBytes() {
|
|
46
|
+
return this.buffer.length - this.offset;
|
|
47
|
+
}
|
|
48
|
+
ensureAvailable(lengthArg) {
|
|
49
|
+
if (lengthArg < 0 || this.buffer.length - this.offset < lengthArg) {
|
|
50
|
+
throw new Error('Malformed ssh-agent response.');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
class SshAgentClient {
|
|
55
|
+
constructor(socketPath = process.env.SSH_AUTH_SOCK ?? '') {
|
|
56
|
+
this.socketPath = socketPath;
|
|
57
|
+
}
|
|
58
|
+
async listIdentities() {
|
|
59
|
+
const response = await this.request(Buffer.from([sshAgentRequestIdentities]));
|
|
60
|
+
const reader = new BinaryReader(response);
|
|
61
|
+
const responseType = reader.readUInt8();
|
|
62
|
+
if (responseType === sshAgentFailure) {
|
|
63
|
+
throw new Error('ssh-agent refused to list identities.');
|
|
64
|
+
}
|
|
65
|
+
if (responseType !== sshAgentIdentitiesAnswer) {
|
|
66
|
+
throw new Error(`Unexpected ssh-agent response type ${responseType}.`);
|
|
67
|
+
}
|
|
68
|
+
const count = reader.readUInt32BE();
|
|
69
|
+
if (count > maxAgentIdentities) {
|
|
70
|
+
throw new Error(`Refusing oversized ssh-agent identity list with ${count} identities.`);
|
|
71
|
+
}
|
|
72
|
+
const identities = [];
|
|
73
|
+
for (let i = 0; i < count; i++) {
|
|
74
|
+
const keyBlob = reader.readSshStringBuffer();
|
|
75
|
+
const comment = reader.readSshStringUtf8();
|
|
76
|
+
const parsedKeyBlob = tryParseSupportedKeyBlob(keyBlob);
|
|
77
|
+
if (!parsedKeyBlob)
|
|
78
|
+
continue;
|
|
79
|
+
const fingerprint = fingerprintSha256(keyBlob);
|
|
80
|
+
const authorizedKey = formatAuthorizedKey(parsedKeyBlob.type, keyBlob, comment);
|
|
81
|
+
identities.push({
|
|
82
|
+
type: parsedKeyBlob.type,
|
|
83
|
+
keyBlob,
|
|
84
|
+
publicKeyBytes: parsedKeyBlob.publicKeyBytes,
|
|
85
|
+
comment,
|
|
86
|
+
fingerprint,
|
|
87
|
+
authorizedKey,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return identities;
|
|
91
|
+
}
|
|
92
|
+
async sign(keyBlobArg, dataArg) {
|
|
93
|
+
const payload = Buffer.concat([
|
|
94
|
+
Buffer.from([sshAgentSignRequest]),
|
|
95
|
+
sshString(keyBlobArg),
|
|
96
|
+
sshString(dataArg),
|
|
97
|
+
uint32(0),
|
|
98
|
+
]);
|
|
99
|
+
const response = await this.request(payload);
|
|
100
|
+
const reader = new BinaryReader(response);
|
|
101
|
+
const responseType = reader.readUInt8();
|
|
102
|
+
if (responseType === sshAgentFailure) {
|
|
103
|
+
throw new Error('ssh-agent refused to sign. The key may be locked, constrained, or missing.');
|
|
104
|
+
}
|
|
105
|
+
if (responseType !== sshAgentSignResponse) {
|
|
106
|
+
throw new Error(`Unexpected ssh-agent response type ${responseType}.`);
|
|
107
|
+
}
|
|
108
|
+
const signatureBlob = reader.readSshStringBuffer();
|
|
109
|
+
const signatureReader = new BinaryReader(signatureBlob);
|
|
110
|
+
const algorithm = signatureReader.readSshStringUtf8();
|
|
111
|
+
const signature = signatureReader.readSshStringBuffer();
|
|
112
|
+
if (signatureReader.remainingBytes !== 0) {
|
|
113
|
+
throw new Error('Malformed ssh-agent signature blob.');
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
algorithm,
|
|
117
|
+
signature,
|
|
118
|
+
signatureBlob,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async request(payloadArg) {
|
|
122
|
+
if (!this.socketPath) {
|
|
123
|
+
throw new Error('SSH_AUTH_SOCK is not set. Start ssh-agent and add the matching SSH key.');
|
|
124
|
+
}
|
|
125
|
+
return await new Promise((resolve, reject) => {
|
|
126
|
+
const socket = net.createConnection({ path: this.socketPath });
|
|
127
|
+
const chunks = [];
|
|
128
|
+
let finished = false;
|
|
129
|
+
let timeoutRef;
|
|
130
|
+
const cleanup = () => {
|
|
131
|
+
if (timeoutRef)
|
|
132
|
+
clearTimeout(timeoutRef);
|
|
133
|
+
};
|
|
134
|
+
const fail = (errorArg) => {
|
|
135
|
+
if (finished)
|
|
136
|
+
return;
|
|
137
|
+
finished = true;
|
|
138
|
+
cleanup();
|
|
139
|
+
socket.destroy();
|
|
140
|
+
reject(errorArg);
|
|
141
|
+
};
|
|
142
|
+
timeoutRef = setTimeout(() => {
|
|
143
|
+
fail(new Error(`ssh-agent request timed out after ${defaultAgentTimeoutMs}ms.`));
|
|
144
|
+
}, defaultAgentTimeoutMs);
|
|
145
|
+
timeoutRef.unref?.();
|
|
146
|
+
socket.on('connect', () => {
|
|
147
|
+
socket.write(Buffer.concat([uint32(payloadArg.length), payloadArg]));
|
|
148
|
+
});
|
|
149
|
+
socket.on('error', fail);
|
|
150
|
+
socket.on('close', () => {
|
|
151
|
+
if (!finished) {
|
|
152
|
+
fail(new Error('ssh-agent closed the connection before sending a complete response.'));
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
socket.on('data', (chunkArg) => {
|
|
156
|
+
if (finished)
|
|
157
|
+
return;
|
|
158
|
+
chunks.push(Buffer.from(chunkArg));
|
|
159
|
+
const buffer = Buffer.concat(chunks);
|
|
160
|
+
if (buffer.length < 4)
|
|
161
|
+
return;
|
|
162
|
+
const responseLength = buffer.readUInt32BE(0);
|
|
163
|
+
if (responseLength > maxAgentResponseBytes) {
|
|
164
|
+
fail(new Error('Refusing oversized ssh-agent response.'));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (buffer.length < 4 + responseLength)
|
|
168
|
+
return;
|
|
169
|
+
finished = true;
|
|
170
|
+
cleanup();
|
|
171
|
+
socket.destroy();
|
|
172
|
+
resolve(buffer.subarray(4, 4 + responseLength));
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
export class SmartconfigSshAgentSecrets {
|
|
178
|
+
static async encryptString(valueArg, optionsArg = {}) {
|
|
179
|
+
const publicKey = await resolvePublicKey(optionsArg);
|
|
180
|
+
const agent = new SshAgentClient(optionsArg.agentSocket);
|
|
181
|
+
const identity = await findIdentity(agent, publicKey);
|
|
182
|
+
const salt = randomBytes(32);
|
|
183
|
+
const challenge = makeChallenge(publicKey, salt);
|
|
184
|
+
const signature = await agent.sign(identity.keyBlob, challenge);
|
|
185
|
+
assertEd25519Signature(signature);
|
|
186
|
+
assertSignatureMatchesPublicKey(publicKey, challenge, signature.signature);
|
|
187
|
+
const kek = deriveKek(signature.signature, salt, publicKey);
|
|
188
|
+
const aad = makeAad(publicKey, salt);
|
|
189
|
+
const box = encryptAes256Gcm(kek, Buffer.from(valueArg, 'utf8'), aad);
|
|
190
|
+
return {
|
|
191
|
+
__smartconfigSecret: secretFormat,
|
|
192
|
+
keyHint: {
|
|
193
|
+
format: 'openssh-authorized-key-v1',
|
|
194
|
+
authorizedKey: publicKey.authorizedKey,
|
|
195
|
+
fingerprint: publicKey.fingerprint,
|
|
196
|
+
},
|
|
197
|
+
payload: {
|
|
198
|
+
encoding: aadEncoding,
|
|
199
|
+
},
|
|
200
|
+
kdf: {
|
|
201
|
+
name: 'HKDF-SHA256',
|
|
202
|
+
salt: toBase64(salt),
|
|
203
|
+
},
|
|
204
|
+
box,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
static async decryptString(encryptedValueArg, optionsArg = {}) {
|
|
208
|
+
const buffer = await this.decryptBuffer(encryptedValueArg, optionsArg);
|
|
209
|
+
return buffer.toString('utf8');
|
|
210
|
+
}
|
|
211
|
+
static async decryptBuffer(encryptedValueArg, optionsArg = {}) {
|
|
212
|
+
const normalized = normalizeEncryptedValue(encryptedValueArg);
|
|
213
|
+
const publicKey = parseOpenSshPublicKeyLine(normalized.keyHint.authorizedKey);
|
|
214
|
+
if (publicKey.fingerprint !== normalized.keyHint.fingerprint) {
|
|
215
|
+
throw new Error('Encrypted value public key does not match its fingerprint.');
|
|
216
|
+
}
|
|
217
|
+
const salt = fromBase64(normalized.kdf.salt, 'kdf.salt', 32);
|
|
218
|
+
const agent = new SshAgentClient(optionsArg.agentSocket);
|
|
219
|
+
const identity = await findIdentity(agent, publicKey);
|
|
220
|
+
const challenge = makeChallenge(publicKey, salt);
|
|
221
|
+
const signature = await agent.sign(identity.keyBlob, challenge);
|
|
222
|
+
assertEd25519Signature(signature);
|
|
223
|
+
assertSignatureMatchesPublicKey(publicKey, challenge, signature.signature);
|
|
224
|
+
const kek = deriveKek(signature.signature, salt, publicKey);
|
|
225
|
+
return decryptAes256Gcm(kek, normalized.box, makeAad(publicKey, salt));
|
|
226
|
+
}
|
|
227
|
+
static isEncryptedValue(valueArg) {
|
|
228
|
+
return Boolean(valueArg
|
|
229
|
+
&& typeof valueArg === 'object'
|
|
230
|
+
&& valueArg.__smartconfigSecret === secretFormat);
|
|
231
|
+
}
|
|
232
|
+
static async writeKey(kvStoreArg, keyArg, valueArg, optionsArg = {}) {
|
|
233
|
+
const encryptedValue = await this.encryptString(valueArg, optionsArg);
|
|
234
|
+
await kvStoreArg.writeKey(keyArg, encryptedValue);
|
|
235
|
+
return encryptedValue;
|
|
236
|
+
}
|
|
237
|
+
static async readKey(kvStoreArg, keyArg, optionsArg = {}) {
|
|
238
|
+
const value = await kvStoreArg.readKey(keyArg);
|
|
239
|
+
if (!this.isEncryptedValue(value)) {
|
|
240
|
+
throw new Error(`Key "${keyArg}" does not contain a smartconfig ssh-agent encrypted value.`);
|
|
241
|
+
}
|
|
242
|
+
return await this.decryptString(value, optionsArg);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async function resolvePublicKey(optionsArg) {
|
|
246
|
+
if (optionsArg.authorizedKey) {
|
|
247
|
+
return parseOpenSshPublicKeyLine(optionsArg.authorizedKey);
|
|
248
|
+
}
|
|
249
|
+
const authorizedKeyPath = optionsArg.authorizedKeyPath ?? '~/.ssh/id_ed25519.pub';
|
|
250
|
+
const content = await fs.readFile(expandHome(authorizedKeyPath), 'utf8');
|
|
251
|
+
return parseOpenSshPublicKeyLine(content);
|
|
252
|
+
}
|
|
253
|
+
async function findIdentity(agentArg, publicKeyArg) {
|
|
254
|
+
const identities = await agentArg.listIdentities();
|
|
255
|
+
const identity = identities.find((candidate) => candidate.fingerprint === publicKeyArg.fingerprint && candidate.keyBlob.equals(publicKeyArg.keyBlob));
|
|
256
|
+
if (!identity) {
|
|
257
|
+
throw new Error(`Required SSH key ${publicKeyArg.fingerprint} is not loaded in ssh-agent.`);
|
|
258
|
+
}
|
|
259
|
+
return identity;
|
|
260
|
+
}
|
|
261
|
+
function normalizeEncryptedValue(valueArg) {
|
|
262
|
+
if (!SmartconfigSshAgentSecrets.isEncryptedValue(valueArg)) {
|
|
263
|
+
throw new Error('Value is not a smartconfig ssh-agent encrypted value.');
|
|
264
|
+
}
|
|
265
|
+
if (valueArg.keyHint?.format !== 'openssh-authorized-key-v1') {
|
|
266
|
+
throw new Error('Encrypted value uses an unsupported key hint format.');
|
|
267
|
+
}
|
|
268
|
+
if (typeof valueArg.keyHint.authorizedKey !== 'string' || !valueArg.keyHint.authorizedKey) {
|
|
269
|
+
throw new Error('Encrypted value is missing an authorized key hint.');
|
|
270
|
+
}
|
|
271
|
+
if (typeof valueArg.keyHint.fingerprint !== 'string' || !valueArg.keyHint.fingerprint.startsWith('SHA256:')) {
|
|
272
|
+
throw new Error('Encrypted value is missing a supported SSH key fingerprint.');
|
|
273
|
+
}
|
|
274
|
+
if (valueArg.payload?.encoding !== aadEncoding) {
|
|
275
|
+
throw new Error('Encrypted value uses an unsupported payload encoding.');
|
|
276
|
+
}
|
|
277
|
+
if (valueArg.kdf?.name !== 'HKDF-SHA256') {
|
|
278
|
+
throw new Error('Encrypted value uses an unsupported KDF.');
|
|
279
|
+
}
|
|
280
|
+
if (valueArg.box?.alg !== 'AES-256-GCM') {
|
|
281
|
+
throw new Error('Encrypted value uses an unsupported encryption algorithm.');
|
|
282
|
+
}
|
|
283
|
+
return valueArg;
|
|
284
|
+
}
|
|
285
|
+
function parseOpenSshPublicKeyLine(lineOrFileArg) {
|
|
286
|
+
const line = lineOrFileArg
|
|
287
|
+
.split(/\r?\n/)
|
|
288
|
+
.map((candidate) => candidate.trim())
|
|
289
|
+
.find((candidate) => candidate && !candidate.startsWith('#'));
|
|
290
|
+
if (!line) {
|
|
291
|
+
throw new Error('No OpenSSH authorized key line found.');
|
|
292
|
+
}
|
|
293
|
+
const tokens = line.split(/\s+/);
|
|
294
|
+
const keyIndex = tokens.findIndex((token) => token === 'ssh-ed25519');
|
|
295
|
+
if (keyIndex === -1 || !tokens[keyIndex + 1]) {
|
|
296
|
+
throw new Error('Only ssh-ed25519 authorized key hints are supported.');
|
|
297
|
+
}
|
|
298
|
+
const keyBlob = fromBase64(tokens[keyIndex + 1], 'authorizedKey.keyBlob');
|
|
299
|
+
const parsedKeyBlob = parseSupportedKeyBlob(keyBlob);
|
|
300
|
+
if (parsedKeyBlob.type !== tokens[keyIndex]) {
|
|
301
|
+
throw new Error(`SSH public key type mismatch: line says ${tokens[keyIndex]}, blob says ${parsedKeyBlob.type}.`);
|
|
302
|
+
}
|
|
303
|
+
const comment = tokens.slice(keyIndex + 2).join(' ');
|
|
304
|
+
const fingerprint = fingerprintSha256(keyBlob);
|
|
305
|
+
return {
|
|
306
|
+
type: parsedKeyBlob.type,
|
|
307
|
+
keyBlob,
|
|
308
|
+
publicKeyBytes: parsedKeyBlob.publicKeyBytes,
|
|
309
|
+
comment,
|
|
310
|
+
fingerprint,
|
|
311
|
+
authorizedKey: formatAuthorizedKey(parsedKeyBlob.type, keyBlob, comment),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function parseSupportedKeyBlob(keyBlobArg) {
|
|
315
|
+
const reader = new BinaryReader(keyBlobArg);
|
|
316
|
+
const type = reader.readSshStringUtf8();
|
|
317
|
+
if (type !== 'ssh-ed25519') {
|
|
318
|
+
throw new Error(`Unsupported SSH key type: ${type}.`);
|
|
319
|
+
}
|
|
320
|
+
const publicKeyBytes = reader.readSshStringBuffer();
|
|
321
|
+
if (publicKeyBytes.length !== 32) {
|
|
322
|
+
throw new Error(`Expected 32-byte Ed25519 public key, got ${publicKeyBytes.length} bytes.`);
|
|
323
|
+
}
|
|
324
|
+
if (reader.remainingBytes !== 0) {
|
|
325
|
+
throw new Error('Malformed ssh-ed25519 public key blob.');
|
|
326
|
+
}
|
|
327
|
+
return { type, publicKeyBytes };
|
|
328
|
+
}
|
|
329
|
+
function tryParseSupportedKeyBlob(keyBlobArg) {
|
|
330
|
+
try {
|
|
331
|
+
return parseSupportedKeyBlob(keyBlobArg);
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
if (error.message.startsWith('Unsupported SSH key type:')) {
|
|
335
|
+
return undefined;
|
|
336
|
+
}
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function assertEd25519Signature(signatureArg) {
|
|
341
|
+
if (signatureArg.algorithm !== 'ssh-ed25519') {
|
|
342
|
+
throw new Error(`Expected ssh-ed25519 signature, got ${signatureArg.algorithm}.`);
|
|
343
|
+
}
|
|
344
|
+
if (signatureArg.signature.length !== 64) {
|
|
345
|
+
throw new Error(`Expected 64-byte Ed25519 signature, got ${signatureArg.signature.length} bytes.`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function assertSignatureMatchesPublicKey(publicKeyArg, challengeArg, signatureArg) {
|
|
349
|
+
const spkiPrefix = Buffer.from('302a300506032b6570032100', 'hex');
|
|
350
|
+
const keyObject = createPublicKey({
|
|
351
|
+
key: Buffer.concat([spkiPrefix, publicKeyArg.publicKeyBytes]),
|
|
352
|
+
format: 'der',
|
|
353
|
+
type: 'spki',
|
|
354
|
+
});
|
|
355
|
+
const valid = verify(null, challengeArg, keyObject, signatureArg);
|
|
356
|
+
if (!valid) {
|
|
357
|
+
throw new Error('ssh-agent signature does not match the encrypted value key hint.');
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function makeChallenge(publicKeyArg, saltArg) {
|
|
361
|
+
return Buffer.concat([
|
|
362
|
+
Buffer.from('smartconfig:ssh-agent-sign-v1:kek\n', 'utf8'),
|
|
363
|
+
sshString(publicKeyArg.fingerprint),
|
|
364
|
+
sshString(publicKeyArg.authorizedKey),
|
|
365
|
+
sshString(saltArg),
|
|
366
|
+
]);
|
|
367
|
+
}
|
|
368
|
+
function deriveKek(signatureArg, saltArg, publicKeyArg) {
|
|
369
|
+
const info = Buffer.concat([
|
|
370
|
+
Buffer.from('smartconfig:ssh-agent-sign-v1:hkdf-info\n', 'utf8'),
|
|
371
|
+
sshString(publicKeyArg.fingerprint),
|
|
372
|
+
sshString(publicKeyArg.authorizedKey),
|
|
373
|
+
]);
|
|
374
|
+
return Buffer.from(hkdfSync('sha256', signatureArg, saltArg, info, 32));
|
|
375
|
+
}
|
|
376
|
+
function makeAad(publicKeyArg, saltArg) {
|
|
377
|
+
return Buffer.from(JSON.stringify({
|
|
378
|
+
format: secretFormat,
|
|
379
|
+
keyHint: {
|
|
380
|
+
format: 'openssh-authorized-key-v1',
|
|
381
|
+
authorizedKey: publicKeyArg.authorizedKey,
|
|
382
|
+
fingerprint: publicKeyArg.fingerprint,
|
|
383
|
+
},
|
|
384
|
+
payload: {
|
|
385
|
+
encoding: aadEncoding,
|
|
386
|
+
},
|
|
387
|
+
kdf: {
|
|
388
|
+
name: 'HKDF-SHA256',
|
|
389
|
+
salt: toBase64(saltArg),
|
|
390
|
+
},
|
|
391
|
+
}), 'utf8');
|
|
392
|
+
}
|
|
393
|
+
function encryptAes256Gcm(keyArg, plaintextArg, aadArg) {
|
|
394
|
+
const nonce = randomBytes(12);
|
|
395
|
+
const cipher = createCipheriv('aes-256-gcm', keyArg, nonce);
|
|
396
|
+
cipher.setAAD(aadArg);
|
|
397
|
+
const ciphertext = Buffer.concat([cipher.update(plaintextArg), cipher.final()]);
|
|
398
|
+
const tag = cipher.getAuthTag();
|
|
399
|
+
return {
|
|
400
|
+
alg: 'AES-256-GCM',
|
|
401
|
+
nonce: toBase64(nonce),
|
|
402
|
+
ciphertext: toBase64(ciphertext),
|
|
403
|
+
tag: toBase64(tag),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function decryptAes256Gcm(keyArg, boxArg, aadArg) {
|
|
407
|
+
if (boxArg.alg !== 'AES-256-GCM') {
|
|
408
|
+
throw new Error(`Unsupported encryption algorithm: ${boxArg.alg}.`);
|
|
409
|
+
}
|
|
410
|
+
const nonce = fromBase64(boxArg.nonce, 'box.nonce', 12);
|
|
411
|
+
const ciphertext = fromBase64(boxArg.ciphertext, 'box.ciphertext');
|
|
412
|
+
const tag = fromBase64(boxArg.tag, 'box.tag', 16);
|
|
413
|
+
const decipher = createDecipheriv('aes-256-gcm', keyArg, nonce);
|
|
414
|
+
decipher.setAAD(aadArg);
|
|
415
|
+
decipher.setAuthTag(tag);
|
|
416
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
417
|
+
}
|
|
418
|
+
function formatAuthorizedKey(typeArg, keyBlobArg, commentArg) {
|
|
419
|
+
return `${typeArg} ${keyBlobArg.toString('base64')}${commentArg ? ` ${commentArg}` : ''}`;
|
|
420
|
+
}
|
|
421
|
+
function fingerprintSha256(keyBlobArg) {
|
|
422
|
+
return `SHA256:${createHash('sha256').update(keyBlobArg).digest('base64').replace(/=+$/g, '')}`;
|
|
423
|
+
}
|
|
424
|
+
function sshString(valueArg) {
|
|
425
|
+
const buffer = Buffer.isBuffer(valueArg) ? valueArg : Buffer.from(valueArg, 'utf8');
|
|
426
|
+
return Buffer.concat([uint32(buffer.length), buffer]);
|
|
427
|
+
}
|
|
428
|
+
function uint32(valueArg) {
|
|
429
|
+
const buffer = Buffer.alloc(4);
|
|
430
|
+
buffer.writeUInt32BE(valueArg, 0);
|
|
431
|
+
return buffer;
|
|
432
|
+
}
|
|
433
|
+
function toBase64(valueArg) {
|
|
434
|
+
return valueArg.toString('base64');
|
|
435
|
+
}
|
|
436
|
+
function fromBase64(valueArg, fieldNameArg, expectedLengthArg) {
|
|
437
|
+
if (typeof valueArg !== 'string' || !valueArg || valueArg.length % 4 === 1 || !/^[A-Za-z0-9+/]+={0,2}$/.test(valueArg)) {
|
|
438
|
+
throw new Error(`${fieldNameArg} must be a base64 string.`);
|
|
439
|
+
}
|
|
440
|
+
const buffer = Buffer.from(valueArg, 'base64');
|
|
441
|
+
if (expectedLengthArg !== undefined && buffer.length !== expectedLengthArg) {
|
|
442
|
+
throw new Error(`${fieldNameArg} must decode to ${expectedLengthArg} bytes.`);
|
|
443
|
+
}
|
|
444
|
+
return buffer;
|
|
445
|
+
}
|
|
446
|
+
function expandHome(pathArg) {
|
|
447
|
+
if (pathArg === '~')
|
|
448
|
+
return homedir();
|
|
449
|
+
if (pathArg.startsWith('~/'))
|
|
450
|
+
return join(homedir(), pathArg.slice(2));
|
|
451
|
+
return pathArg;
|
|
452
|
+
}
|
|
453
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRjb25maWcuc3NoYWdlbnRzZWNyZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9zc2hhZ2VudC9zbWFydGNvbmZpZy5zc2hhZ2VudHNlY3JldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixVQUFVLEVBQ1YsZUFBZSxFQUNmLFFBQVEsRUFDUixXQUFXLEVBQ1gsTUFBTSxHQUNQLE1BQU0sYUFBYSxDQUFDO0FBQ3JCLE9BQU8sRUFBRSxRQUFRLElBQUksRUFBRSxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQ3pDLE9BQU8sS0FBSyxHQUFHLE1BQU0sVUFBVSxDQUFDO0FBQ2hDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFDbEMsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLFdBQVcsQ0FBQztBQW1FakMsTUFBTSxlQUFlLEdBQUcsQ0FBQyxDQUFDO0FBQzFCLE1BQU0seUJBQXlCLEdBQUcsRUFBRSxDQUFDO0FBQ3JDLE1BQU0sd0JBQXdCLEdBQUcsRUFBRSxDQUFDO0FBQ3BDLE1BQU0sbUJBQW1CLEdBQUcsRUFBRSxDQUFDO0FBQy9CLE1BQU0sb0JBQW9CLEdBQUcsRUFBRSxDQUFDO0FBQ2hDLE1BQU0scUJBQXFCLEdBQUcsRUFBRSxHQUFHLElBQUksR0FBRyxJQUFJLENBQUM7QUFDL0MsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLENBQUM7QUFDaEMsTUFBTSxxQkFBcUIsR0FBRyxNQUFNLENBQUM7QUFDckMsTUFBTSxZQUFZLEdBQTZCLG1CQUFtQixDQUFDO0FBQ25FLE1BQU0sV0FBVyxHQUFzQyxNQUFNLENBQUM7QUFFOUQsTUFBTSxZQUFZO0lBR2hCLFlBQTZCLE1BQWM7UUFBZCxXQUFNLEdBQU4sTUFBTSxDQUFRO1FBRm5DLFdBQU0sR0FBRyxDQUFDLENBQUM7SUFFMkIsQ0FBQztJQUV4QyxTQUFTO1FBQ2QsSUFBSSxDQUFDLGVBQWUs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
|
package/package.json
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartconfig",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A comprehensive configuration management library providing key-value storage, environment variable mapping, and tool configuration.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
7
7
|
"typings": "dist_ts/index.d.ts",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist_ts/index.d.ts",
|
|
11
|
+
"import": "./dist_ts/index.js",
|
|
12
|
+
"default": "./dist_ts/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./sshagent": {
|
|
15
|
+
"types": "./dist_ts/sshagent/index.d.ts",
|
|
16
|
+
"import": "./dist_ts/sshagent/index.js",
|
|
17
|
+
"default": "./dist_ts/sshagent/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./dist_ts/*": "./dist_ts/*",
|
|
20
|
+
"./ts/*": "./ts/*",
|
|
21
|
+
"./package.json": "./package.json"
|
|
12
22
|
},
|
|
13
23
|
"repository": {
|
|
14
24
|
"type": "git",
|
|
@@ -32,11 +42,11 @@
|
|
|
32
42
|
"@tsclass/tsclass": "^9.5.1"
|
|
33
43
|
},
|
|
34
44
|
"devDependencies": {
|
|
35
|
-
"@git.zone/tsbuild": "^4.4.
|
|
36
|
-
"@git.zone/tsrun": "^2.0.
|
|
37
|
-
"@git.zone/tstest": "^
|
|
45
|
+
"@git.zone/tsbuild": "^4.4.2",
|
|
46
|
+
"@git.zone/tsrun": "^2.0.6",
|
|
47
|
+
"@git.zone/tstest": "^4.0.0",
|
|
38
48
|
"@types/lodash.clonedeep": "^4.5.9",
|
|
39
|
-
"@types/node": "
|
|
49
|
+
"@types/node": "26.4.0"
|
|
40
50
|
},
|
|
41
51
|
"files": [
|
|
42
52
|
"ts/**/*",
|
|
@@ -72,5 +82,9 @@
|
|
|
72
82
|
"workflow improvement",
|
|
73
83
|
"persistent storage"
|
|
74
84
|
],
|
|
75
|
-
"
|
|
76
|
-
|
|
85
|
+
"scripts": {
|
|
86
|
+
"test": "(tstest test/ --verbose --logfile --timeout 20)",
|
|
87
|
+
"build": "(tsbuild --web)",
|
|
88
|
+
"buildDocs": "tsdoc"
|
|
89
|
+
}
|
|
90
|
+
}
|
package/readme.md
CHANGED
|
@@ -133,6 +133,61 @@ kv.changeSubject.subscribe((data) => console.log('changed:', data));
|
|
|
133
133
|
|
|
134
134
|
---
|
|
135
135
|
|
|
136
|
+
### SSH-Agent Encrypted Single Values
|
|
137
|
+
|
|
138
|
+
For Node.js CLI tools, individual `KeyValueStore` values can be stored as encrypted JSON envelopes and unlocked later through a matching SSH key loaded in `ssh-agent`. This API is intentionally exposed from the Node-only subpath `@push.rocks/smartconfig/sshagent` so browser bundles do not pull in `node:net`, `node:fs`, or `node:crypto`.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { KeyValueStore } from '@push.rocks/smartconfig';
|
|
142
|
+
import { SmartconfigSshAgentSecrets } from '@push.rocks/smartconfig/sshagent';
|
|
143
|
+
|
|
144
|
+
const kv = new KeyValueStore({
|
|
145
|
+
typeArg: 'userHomeDir',
|
|
146
|
+
identityArg: 'my-tool',
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await SmartconfigSshAgentSecrets.writeKey(kv, 'apiToken', 'secret-token', {
|
|
150
|
+
authorizedKeyPath: '~/.ssh/id_ed25519.pub',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const apiToken = await SmartconfigSshAgentSecrets.readKey(kv, 'apiToken');
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The stored value is a single self-describing object. Other keys in the same config can remain plain JSON values.
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"apiUrl": "https://api.example.test",
|
|
161
|
+
"apiToken": {
|
|
162
|
+
"__smartconfigSecret": "ssh-agent-sign-v1",
|
|
163
|
+
"keyHint": {
|
|
164
|
+
"format": "openssh-authorized-key-v1",
|
|
165
|
+
"authorizedKey": "ssh-ed25519 AAAAC3... user@host",
|
|
166
|
+
"fingerprint": "SHA256:..."
|
|
167
|
+
},
|
|
168
|
+
"payload": {
|
|
169
|
+
"encoding": "utf8"
|
|
170
|
+
},
|
|
171
|
+
"kdf": {
|
|
172
|
+
"name": "HKDF-SHA256",
|
|
173
|
+
"salt": "..."
|
|
174
|
+
},
|
|
175
|
+
"box": {
|
|
176
|
+
"alg": "AES-256-GCM",
|
|
177
|
+
"nonce": "...",
|
|
178
|
+
"ciphertext": "...",
|
|
179
|
+
"tag": "..."
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The encrypted value includes an OpenSSH authorized-key-style hint. Smartconfig uses the hint's public key blob and `SHA256:` fingerprint to find the matching identity in `ssh-agent`; the key comment is not trusted for matching.
|
|
186
|
+
|
|
187
|
+
This is ssh-agent-signature-derived encryption, not public-key encryption. Encryption and decryption both require the matching `ssh-ed25519` key to be available through `ssh-agent`. Anyone who can access your `SSH_AUTH_SOCK` can ask the agent to sign the unlock challenge, so this protects config values at rest on disk, not an already-compromised unlocked session.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
136
191
|
### 3. `AppData<T>` — Environment Variable Mapping 🌟
|
|
137
192
|
|
|
138
193
|
The flagship class. Maps environment variables (or hardcoded values) into a typed config object with automatic type conversions, nested object support, and smart storage path selection.
|
|
@@ -250,7 +305,19 @@ const port = await AppData.valueAsNumber('PORT');
|
|
|
250
305
|
|
|
251
306
|
## Security 🔐
|
|
252
307
|
|
|
253
|
-
AppData
|
|
308
|
+
AppData diagnostics report configuration key names, environment-variable names,
|
|
309
|
+
source kinds, transforms, presence and value types, never configuration values.
|
|
310
|
+
This applies to ordinary-looking names as well as credentials, nested values,
|
|
311
|
+
hardcoded literals, boolean conversions and overrides. No prefixes or previews
|
|
312
|
+
of secret values are emitted. Mapping-failure diagnostics identify the affected
|
|
313
|
+
key without logging arbitrary exception messages or objects, which may contain
|
|
314
|
+
values. Keep key names, environment-variable names and storage paths free of secrets.
|
|
315
|
+
|
|
316
|
+
This logging guarantee does not encrypt stored values or change configuration
|
|
317
|
+
results. AppData persists evaluated values by default; use `ephemeral: true` for
|
|
318
|
+
runtime secrets that must stay in memory. Previously exposed credentials must
|
|
319
|
+
be rotated or revoked in their owning service; upgrading the logger does not
|
|
320
|
+
invalidate credentials or remove historical logs.
|
|
254
321
|
|
|
255
322
|
---
|
|
256
323
|
|
|
@@ -302,7 +369,7 @@ console.log(`Model: ${settings.model}, Cache: ${settings.cache.enabled}`);
|
|
|
302
369
|
|
|
303
370
|
## License and Legal Information
|
|
304
371
|
|
|
305
|
-
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [
|
|
372
|
+
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.
|
|
306
373
|
|
|
307
374
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
|
308
375
|
|
package/ts/00_commitinfo_data.ts
CHANGED