@push.rocks/smartconfig 6.1.0 → 6.2.0
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 +7 -6
- package/dist_ts/classes.keyvaluestore.d.ts +1 -1
- package/dist_ts/classes.keyvaluestore.js +18 -10
- package/dist_ts/classes.smartconfig.js +4 -3
- package/dist_ts/plugins.d.ts +4 -2
- package/dist_ts/plugins.js +5 -3
- 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 +31 -18
- package/readme.md +56 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.appdata.ts +8 -7
- package/ts/classes.keyvaluestore.ts +17 -12
- package/ts/classes.smartconfig.ts +5 -7
- package/ts/plugins.ts +6 -2
- 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.0",
|
|
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",
|
|
@@ -22,20 +32,21 @@
|
|
|
22
32
|
"homepage": "https://code.foss.global/push.rocks/smartconfig#readme",
|
|
23
33
|
"dependencies": {
|
|
24
34
|
"@push.rocks/qenv": "^6.1.3",
|
|
25
|
-
"@push.rocks/
|
|
26
|
-
"@push.rocks/smartjson": "^
|
|
27
|
-
"@push.rocks/smartlog": "^3.
|
|
35
|
+
"@push.rocks/smartfs": "^1.5.1",
|
|
36
|
+
"@push.rocks/smartjson": "^6.0.1",
|
|
37
|
+
"@push.rocks/smartlog": "^3.2.2",
|
|
28
38
|
"@push.rocks/smartpath": "^6.0.0",
|
|
29
39
|
"@push.rocks/smartpromise": "^4.2.3",
|
|
30
40
|
"@push.rocks/smartrx": "^3.0.10",
|
|
31
|
-
"@push.rocks/taskbuffer": "^
|
|
32
|
-
"@tsclass/tsclass": "^9.
|
|
41
|
+
"@push.rocks/taskbuffer": "^8.0.2",
|
|
42
|
+
"@tsclass/tsclass": "^9.5.1"
|
|
33
43
|
},
|
|
34
44
|
"devDependencies": {
|
|
35
|
-
"@git.zone/tsbuild": "^
|
|
36
|
-
"@git.zone/tsrun": "^
|
|
37
|
-
"@git.zone/tstest": "^
|
|
38
|
-
"@types/
|
|
45
|
+
"@git.zone/tsbuild": "^4.4.0",
|
|
46
|
+
"@git.zone/tsrun": "^2.0.2",
|
|
47
|
+
"@git.zone/tstest": "^3.6.3",
|
|
48
|
+
"@types/lodash.clonedeep": "^4.5.9",
|
|
49
|
+
"@types/node": "^25.6.0"
|
|
39
50
|
},
|
|
40
51
|
"files": [
|
|
41
52
|
"ts/**/*",
|
|
@@ -47,6 +58,7 @@
|
|
|
47
58
|
"assets/**/*",
|
|
48
59
|
"cli.js",
|
|
49
60
|
".smartconfig.json",
|
|
61
|
+
"license",
|
|
50
62
|
"readme.md"
|
|
51
63
|
],
|
|
52
64
|
"browserslist": [
|
|
@@ -70,8 +82,9 @@
|
|
|
70
82
|
"workflow improvement",
|
|
71
83
|
"persistent storage"
|
|
72
84
|
],
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
"
|
|
85
|
+
"scripts": {
|
|
86
|
+
"test": "(tstest test/ --verbose --logfile --timeout 20)",
|
|
87
|
+
"build": "(tsbuild --web)",
|
|
88
|
+
"buildDocs": "tsdoc"
|
|
76
89
|
}
|
|
77
|
-
}
|
|
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.
|
|
@@ -302,7 +357,7 @@ console.log(`Model: ${settings.model}, Cache: ${settings.cache.enabled}`);
|
|
|
302
357
|
|
|
303
358
|
## License and Legal Information
|
|
304
359
|
|
|
305
|
-
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [
|
|
360
|
+
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
361
|
|
|
307
362
|
**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
363
|
|
package/ts/00_commitinfo_data.ts
CHANGED