CH-J Toolsmanagementul serverului peste SSH
Meniu
Sursă publicată

CH-J Tools

Navighează directoarele și fișierele pentru o anumită versiune a aplicației.

Descarcă sursa ZIP
CH-J / Server Manager 406 fișiere
core-20261024T135205Z-7c487d719de2 Adăugat: · Construit:
keyGeneratorService.js Adăugat: Data neînregistrată 22,0 KB · 380 linii
Cod Descarcă fișierul
1"use strict";
3const crypto = require("node:crypto");
4const fs = require("node:fs");
5const path = require("node:path");
7const TYPES = new Set(["ed25519", "rsa", "ecdsa", "dsa", "rsa1"]);
8const PRIVATE_KEY_FORMATS = new Set(["pem", "openssh", "ppk"]);
9const BITS = Object.freeze({ rsa: [2048, 3072, 4096], ecdsa: [256, 384, 521], dsa: [1024], rsa1: [1024] });
10const CURVES = Object.freeze({
11 256: ["prime256v1", "ecdsa-sha2-nistp256", "nistp256", 32],
12 384: ["secp384r1", "ecdsa-sha2-nistp384", "nistp384", 48],
13 521: ["secp521r1", "ecdsa-sha2-nistp521", "nistp521", 66]
14});
16function decodeBase64Url(value) {
17 const source = String(value || "").replace(/-/g, "+").replace(/_/g, "/");
18 return Buffer.from(source.padEnd(Math.ceil(source.length / 4) * 4, "="), "base64");
21function sshUint32(value) {
22 const result = Buffer.alloc(4); result.writeUInt32BE(Number(value) >>> 0); return result;
25function sshString(value) {
26 const data = Buffer.isBuffer(value) ? value : Buffer.from(String(value), "utf8");
27 return Buffer.concat([sshUint32(data.length), data]);
30function stripLeadingZeroBytes(value) {
31 let data = Buffer.from(value || []);
32 while (data.length > 1 && data[0] === 0) data = data.subarray(1);
33 return data;
36function sshMpint(value) {
37 let data = stripLeadingZeroBytes(value);
38 if (!data.length || data.every((byte) => byte === 0)) data = Buffer.alloc(0);
39 else if (data[0] & 0x80) data = Buffer.concat([Buffer.from([0]), data]);
40 return sshString(data);
43function comment(value) {
44 return String(value || "").replace(/[\r\n\t]+/g, " ").trim().slice(0, 200);
47function normalize(payload = {}) {
48 const type = String(payload.type || "ed25519").toLowerCase();
49 if (!TYPES.has(type)) throw new Error("Unsupported key type.");
50 if ((type === "dsa" || type === "rsa1") && payload.legacyConfirmed !== true) throw new Error("Legacy key generation requires explicit confirmation.");
51 const allowed = BITS[type] || [];
52 const defaults = { rsa: 4096, ecdsa: 256, dsa: 1024, rsa1: 1024 };
53 const bits = allowed.includes(Number(payload.bits)) ? Number(payload.bits) : (defaults[type] || 0);
54 const passphrase = String(payload.passphrase || "");
55 if (passphrase.length > 1024 || passphrase.includes("\0")) throw new Error("Invalid key passphrase.");
56 const requestedFormat = String(payload.privateKeyFormat || "pem").toLowerCase();
57 if (!PRIVATE_KEY_FORMATS.has(requestedFormat)) throw new Error("Unsupported private key format.");
58 return { type, bits, passphrase, privateKeyFormat: requestedFormat, comment: comment(payload.comment) };
61function generatePair(type, bits) {
62 if (type === "ed25519") return crypto.generateKeyPairSync("ed25519");
63 if (type === "rsa" || type === "rsa1") return crypto.generateKeyPairSync("rsa", { modulusLength: bits, publicExponent: 0x10001 });
64 if (type === "ecdsa") return crypto.generateKeyPairSync("ec", { namedCurve: CURVES[bits][0] });
65 return generateDsaPair();
68// ---------- DSA (legacy) ----------
69// Electron's BoringSSL cannot generate DSA keys (generateKeyPairSync("dsa") fails with "Key generation job failed"),
70// but it can import them. The domain parameters and the key are therefore generated here (FIPS 186-4 A.1.1.2 style
71// probable primes from the platform CSPRNG and Miller-Rabin of checkPrimeSync) and imported as a normal KeyObject,
72// so the PEM/OpenSSH/PPK export path is the same as for every other key type.
73function modPow(base, exponent, modulus) {
74 let result = 1n; let value = base % modulus; let power = exponent;
75 while (power > 0n) {
76 if (power & 1n) result = (result * value) % modulus;
77 value = (value * value) % modulus; power >>= 1n;
78 }
79 return result;
81function randomBits(bits) {
82 const bytes = crypto.randomBytes(Math.ceil(bits / 8));
83 bytes[0] &= 0xff >> (bytes.length * 8 - bits);
84 return BigInt(`0x${bytes.toString("hex")}`);
86function bitLength(value) { return value.toString(2).length; }
87function generateDsaNumbers(L = 1024, N = 160) {
88 for (let round = 0; round < 64; round += 1) {
89 const q = crypto.generatePrimeSync(N, { bigint: true });
90 if (bitLength(q) !== N) continue;
91 const twoQ = 2n * q; const top = 1n << BigInt(L - 1);
92 for (let counter = 0; counter < 4 * L; counter += 1) {
93 const X = randomBits(L) | top;
94 const p = X - ((X % twoQ) - 1n);
95 if (p < top || !crypto.checkPrimeSync(p, { checks: 64 })) continue;
96 const e = (p - 1n) / q;
97 let g = 1n;
98 for (let h = 2n; g === 1n && h < p - 1n; h += 1n) g = modPow(h, e, p);
99 let x = 0n;
100 while (x < 1n || x >= q) x = randomBits(N);
101 return { p, q, g, x, y: modPow(g, x, p) };
102 }
103 }
104 throw new Error("DSA parameters could not be generated.");
106function derLength(length) {
107 if (length < 0x80) return Buffer.from([length]);
108 const bytes = []; for (let value = length; value > 0; value = Math.floor(value / 256)) bytes.unshift(value & 0xff);
109 return Buffer.from([0x80 | bytes.length, ...bytes]);
111function der(tag, content) { return Buffer.concat([Buffer.from([tag]), derLength(content.length), content]); }
112function derBigInt(value) {
113 let hex = value.toString(16); if (hex.length % 2) hex = `0${hex}`;
114 let bytes = Buffer.from(hex, "hex"); if (bytes[0] & 0x80) bytes = Buffer.concat([Buffer.from([0]), bytes]);
115 return der(0x02, bytes);
117const DSA_OID = Buffer.from([0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x38, 0x04, 0x01]);
118function generateDsaPair() {
119 const { p, q, g, x, y } = generateDsaNumbers();
120 const algorithm = der(0x30, Buffer.concat([DSA_OID, der(0x30, Buffer.concat([derBigInt(p), derBigInt(q), derBigInt(g)]))]));
121 const pkcs8 = der(0x30, Buffer.concat([derBigInt(0n), algorithm, der(0x04, derBigInt(x))]));
122 const privateKey = crypto.createPrivateKey({ key: pkcs8, format: "der", type: "pkcs8" });
123 const publicKey = crypto.createPublicKey(privateKey);
124 // The public value derived by the crypto library must match y = g^x mod p.
125 if (BigInt(`0x${dsaPublicNumbers(publicKey).y.toString("hex")}`) !== y) throw new Error("DSA self-check failed.");
126 return { privateKey, publicKey };
129// ---------- passwords ----------
130// Character sets as requested for the Key Generator; digits are 1-9 (no 0, avoids confusion with O).
131const PASSWORD_SETS = Object.freeze({
132 upper: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
133 lower: "abcdefghijklmnopqrstuvwxyz",
134 digits: "123456789",
135 special: "-@)\u00a8\u00a7'=+"
136});
137const PASSWORD_LENGTH = Object.freeze({ min: 4, max: 70 });
138const PASSWORD_GROUP = 5;
139// Uniform choice with crypto.randomInt; with grouping every 5th character is a fixed "-" (xxxx-yyyy-cccc-) and "-" is
140// not used as a random character. Each selected set appears at least once (rejection sampling keeps it uniform).
141function generatePassword(options = {}) {
142 const length = Number(options.length);
143 if (!Number.isInteger(length) || length < PASSWORD_LENGTH.min || length > PASSWORD_LENGTH.max) throw new Error(`Password length must be ${PASSWORD_LENGTH.min}-${PASSWORD_LENGTH.max} characters.`);
144 const grouped = options.grouped === true;
145 const sets = Object.keys(PASSWORD_SETS).filter((key) => options[key] === true)
146 .map((key) => [...(grouped ? PASSWORD_SETS[key].replace("-", "") : PASSWORD_SETS[key])]).filter((set) => set.length);
147 if (!sets.length) throw new Error("Select at least one character set.");
148 const pool = [...new Set(sets.flat())];
149 const separator = (index) => grouped && (index + 1) % PASSWORD_GROUP === 0;
150 const slots = Array.from({ length }, (_value, index) => !separator(index)).filter(Boolean).length;
151 if (slots < sets.length) throw new Error(`The length leaves ${slots} random characters, but ${sets.length} character sets are selected.`);
152 for (let attempt = 0; attempt < 10000; attempt += 1) {
153 const chars = Array.from({ length: slots }, () => pool[crypto.randomInt(pool.length)]);
154 if (!sets.every((set) => chars.some((char) => set.includes(char)))) continue;
155 let next = 0;
156 const password = Array.from({ length }, (_value, index) => (separator(index) ? "-" : chars[next++])).join("");
157 return { password, length, randomCharacters: slots, poolSize: pool.length, entropyBits: Math.floor(slots * Math.log2(pool.length)) };
158 }
159 throw new Error("The password could not be generated with these settings.");
162function readDerLength(buffer, offset) {
163 if (offset >= buffer.length) throw new Error("Invalid DER length.");
164 const first = buffer[offset];
165 if ((first & 0x80) === 0) return { length: first, nextOffset: offset + 1 };
166 const bytes = first & 0x7f;
167 if (!bytes || offset + 1 + bytes > buffer.length) throw new Error("Invalid DER length bytes.");
168 let length = 0;
169 for (let index = 0; index < bytes; index += 1) length = (length * 256) + buffer[offset + 1 + index];
170 if (!Number.isSafeInteger(length)) throw new Error("Unsupported DER length size.");
171 return { length, nextOffset: offset + 1 + bytes };
174function readDerElement(buffer, offset = 0) {
175 if (!Buffer.isBuffer(buffer) || offset < 0 || offset >= buffer.length) throw new Error("Invalid DER input.");
176 const lengthInfo = readDerLength(buffer, offset + 1);
177 const valueStart = lengthInfo.nextOffset; const valueEnd = valueStart + lengthInfo.length;
178 if (valueEnd > buffer.length) throw new Error("Invalid DER element bounds.");
179 return { tag: buffer[offset], valueStart, valueEnd, nextOffset: valueEnd };
182function derInteger(buffer, element) {
183 if (element.tag !== 0x02) throw new Error("Expected DER INTEGER.");
184 return stripLeadingZeroBytes(buffer.subarray(element.valueStart, element.valueEnd));
187function dsaParameters(buffer, algorithm) {
188 let cursor = algorithm.valueStart;
189 const oid = readDerElement(buffer, cursor); cursor = oid.nextOffset;
190 const params = readDerElement(buffer, cursor);
191 if (oid.tag !== 0x06 || params.tag !== 0x30 || params.nextOffset !== algorithm.valueEnd) throw new Error("Invalid DSA parameters.");
192 cursor = params.valueStart;
193 const p = readDerElement(buffer, cursor); cursor = p.nextOffset;
194 const q = readDerElement(buffer, cursor); cursor = q.nextOffset;
195 const g = readDerElement(buffer, cursor); cursor = g.nextOffset;
196 if (cursor !== params.valueEnd) throw new Error("Invalid DSA parameter payload.");
197 return { p: derInteger(buffer, p), q: derInteger(buffer, q), g: derInteger(buffer, g) };
200function dsaPublicNumbers(publicKey) {
201 const buffer = Buffer.from(publicKey.export({ type: "spki", format: "der" }));
202 const root = readDerElement(buffer); let cursor = root.valueStart;
203 const algorithm = readDerElement(buffer, cursor); cursor = algorithm.nextOffset;
204 const subject = readDerElement(buffer, cursor);
205 if (root.tag !== 0x30 || root.nextOffset !== buffer.length || algorithm.tag !== 0x30 || subject.tag !== 0x03 || subject.nextOffset !== root.valueEnd) throw new Error("Invalid DSA SPKI structure.");
206 const encoded = buffer.subarray(subject.valueStart + 1, subject.valueEnd);
207 const y = readDerElement(encoded);
208 if (buffer[subject.valueStart] !== 0 || y.nextOffset !== encoded.length) throw new Error("Invalid DSA public value.");
209 return { ...dsaParameters(buffer, algorithm), y: derInteger(encoded, y) };
212function dsaPrivateNumbers(privateKey) {
213 const buffer = Buffer.from(privateKey.export({ type: "pkcs8", format: "der" }));
214 const root = readDerElement(buffer); let cursor = root.valueStart;
215 const version = readDerElement(buffer, cursor); cursor = version.nextOffset;
216 const algorithm = readDerElement(buffer, cursor); cursor = algorithm.nextOffset;
217 const octet = readDerElement(buffer, cursor);
218 if (root.tag !== 0x30 || root.nextOffset !== buffer.length || version.tag !== 0x02 || algorithm.tag !== 0x30 || octet.tag !== 0x04 || octet.nextOffset !== root.valueEnd) throw new Error("Invalid DSA PKCS#8 structure.");
219 const encoded = buffer.subarray(octet.valueStart, octet.valueEnd); const x = readDerElement(encoded);
220 if (x.nextOffset !== encoded.length) throw new Error("Invalid DSA private value.");
221 return { ...dsaParameters(buffer, algorithm), x: derInteger(encoded, x) };
224function keyNumbers(type, pair) {
225 if (type === "dsa") return { ...dsaPublicNumbers(pair.publicKey), ...dsaPrivateNumbers(pair.privateKey) };
226 const privateJwk = pair.privateKey.export({ format: "jwk" });
227 const publicJwk = pair.publicKey.export({ format: "jwk" });
228 if (type === "ed25519") return { seed: decodeBase64Url(privateJwk.d), publicRaw: decodeBase64Url(publicJwk.x) };
229 if (type === "rsa" || type === "rsa1") return Object.fromEntries(["n", "e", "d", "p", "q", "qi"].map((key) => [key, stripLeadingZeroBytes(decodeBase64Url(privateJwk[key]))]));
230 return { d: stripLeadingZeroBytes(decodeBase64Url(privateJwk.d)), x: decodeBase64Url(publicJwk.x), y: decodeBase64Url(publicJwk.y) };
233function publicMaterial(type, bits, numbers, keyComment) {
234 const suffix = keyComment ? ` ${keyComment}` : "";
235 if (type === "ed25519") {
236 const algorithm = "ssh-ed25519";
237 const blob = Buffer.concat([sshString(algorithm), sshString(numbers.publicRaw)]);
238 return { algorithm, blob, line: `${algorithm} ${blob.toString("base64")}${suffix}` };
239 }
240 if (type === "rsa" || type === "rsa1") {
241 if (type === "rsa1") {
242 const exponent = BigInt(`0x${numbers.e.toString("hex")}`).toString();
243 const modulus = BigInt(`0x${numbers.n.toString("hex")}`).toString();
244 return { algorithm: "ssh1-rsa", blob: Buffer.concat([sshMpint(numbers.e), sshMpint(numbers.n)]), line: `${bits} ${exponent} ${modulus}${suffix}` };
245 }
246 const algorithm = "ssh-rsa"; const blob = Buffer.concat([sshString(algorithm), sshMpint(numbers.e), sshMpint(numbers.n)]);
247 return { algorithm, blob, line: `${algorithm} ${blob.toString("base64")}${suffix}` };
248 }
249 if (type === "dsa") {
250 const algorithm = "ssh-dss";
251 const blob = Buffer.concat([sshString(algorithm), sshMpint(numbers.p), sshMpint(numbers.q), sshMpint(numbers.g), sshMpint(numbers.y)]);
252 return { algorithm, blob, line: `${algorithm} ${blob.toString("base64")}${suffix}` };
253 }
254 const [, algorithm, curve, size] = CURVES[bits];
255 const x = numbers.x.length === size ? numbers.x : Buffer.concat([Buffer.alloc(size - numbers.x.length), numbers.x]);
256 const y = numbers.y.length === size ? numbers.y : Buffer.concat([Buffer.alloc(size - numbers.y.length), numbers.y]);
257 const point = Buffer.concat([Buffer.from([4]), x, y]);
258 const blob = Buffer.concat([sshString(algorithm), sshString(curve), sshString(point)]);
259 return { algorithm, curve, point, blob, line: `${algorithm} ${blob.toString("base64")}${suffix}` };
262function pemBlock(name, raw) {
263 const base64 = Buffer.from(raw).toString("base64"); const lines = [];
264 for (let index = 0; index < base64.length; index += 70) lines.push(base64.slice(index, index + 70));
265 return `-----BEGIN ${name}-----\n${lines.join("\n")}\n-----END ${name}-----\n`;
268function openSshPrivate(type, numbers, material, keyComment) {
269 const check = crypto.randomBytes(4); let fields;
270 if (type === "ed25519") fields = [sshString(material.algorithm), sshString(numbers.publicRaw), sshString(Buffer.concat([numbers.seed, numbers.publicRaw]))];
271 else if (type === "rsa") fields = [sshString(material.algorithm), sshMpint(numbers.n), sshMpint(numbers.e), sshMpint(numbers.d), sshMpint(numbers.qi), sshMpint(numbers.p), sshMpint(numbers.q)];
272 else if (type === "dsa") fields = [sshString(material.algorithm), sshMpint(numbers.p), sshMpint(numbers.q), sshMpint(numbers.g), sshMpint(numbers.y), sshMpint(numbers.x)];
273 else fields = [sshString(material.algorithm), sshString(material.curve), sshString(material.point), sshMpint(numbers.d)];
274 let privateSection = Buffer.concat([check, check, ...fields, sshString(keyComment)]);
275 const paddingLength = (8 - (privateSection.length % 8)) % 8;
276 if (paddingLength) privateSection = Buffer.concat([privateSection, Buffer.from(Array.from({ length: paddingLength }, (_unused, index) => index + 1))]);
277 const raw = Buffer.concat([
278 Buffer.from("openssh-key-v1\0", "ascii"), sshString("none"), sshString("none"), sshString(Buffer.alloc(0)),
279 sshUint32(1), sshString(material.blob), sshString(privateSection)
280 ]);
281 return pemBlock("OPENSSH PRIVATE KEY", raw);
284function ppkPrivateBlob(type, numbers) {
285 if (type === "ed25519") return sshString(Buffer.concat([numbers.seed, numbers.publicRaw]));
286 if (type === "rsa" || type === "rsa1") return Buffer.concat([sshMpint(numbers.d), sshMpint(numbers.p), sshMpint(numbers.q), sshMpint(numbers.qi)]);
287 if (type === "dsa") return sshMpint(numbers.x);
288 return sshMpint(numbers.d);
291function ppkV2(type, material, numbers, keyComment) {
292 const publicBlob = material.blob; const privateBlob = ppkPrivateBlob(type, numbers);
293 const macData = Buffer.concat([sshString(material.algorithm), sshString("none"), sshString(keyComment), sshString(publicBlob), sshString(privateBlob)]);
294 const macKey = crypto.createHash("sha1").update("putty-private-key-file-mac-key", "utf8").digest();
295 const privateMac = crypto.createHmac("sha1", macKey).update(macData).digest("hex");
296 const lines = (buffer) => buffer.toString("base64").match(/.{1,64}/g) || [""];
297 const publicLines = lines(publicBlob); const privateLines = lines(privateBlob);
298 return [`PuTTY-User-Key-File-2: ${material.algorithm}`, "Encryption: none", `Comment: ${keyComment}`, `Public-Lines: ${publicLines.length}`, ...publicLines, `Private-Lines: ${privateLines.length}`, ...privateLines, `Private-MAC: ${privateMac}`].join("\n") + "\n";
301function exportPem(privateKey, passphrase) {
302 const options = { type: "pkcs8", format: "pem" };
303 if (passphrase) Object.assign(options, { cipher: "aes-256-cbc", passphrase });
304 return String(privateKey.export(options));
307function chooseFormat(input) {
308 if (input.type === "rsa1") {
309 if (input.passphrase) throw new Error("SSH1 RSA PPK export cannot be protected by a passphrase. Remove it or use a modern key type.");
310 return { format: "ppk", notice: input.privateKeyFormat === "ppk" ? "SSH1 RSA je zastaralý formát." : "SSH1 RSA se ukládá jako PuTTY PPK v2." };
311 }
312 if (input.passphrase && input.privateKeyFormat !== "pem") {
313 return { format: "pem", notice: `${input.privateKeyFormat === "ppk" ? "PPK v2" : "OpenSSH"} export s passphrase není ve vestavěném backendu dostupný; byl bezpečně použit šifrovaný PEM/PKCS#8.` };
314 }
315 return { format: input.privateKeyFormat, notice: input.privateKeyFormat === "ppk" ? "PPK v2 je určen hlavně pro kompatibilitu se staršími nástroji PuTTY." : "" };
318class KeyGeneratorService {
319 constructor(options = {}) {
320 this.selectSavePath = options.selectSavePath;
321 this.writeClipboard = options.writeClipboard;
322 this.generations = new Map();
323 this.passwords = new Map();
324 }
326 generate(payload) {
327 const input = normalize(payload); const pair = generatePair(input.type, input.bits);
328 const numbers = keyNumbers(input.type, pair); const material = publicMaterial(input.type, input.bits, numbers, input.comment);
329 const privateKeyPem = exportPem(pair.privateKey, input.passphrase);
330 const selected = chooseFormat(input);
331 const privateKeyOpenSSH = selected.format === "openssh" ? openSshPrivate(input.type, numbers, material, input.comment) : "";
332 const privateKeyPpk = selected.format === "ppk" ? ppkV2(input.type, material, numbers, input.comment) : "";
333 const privateKey = selected.format === "openssh" ? privateKeyOpenSSH : selected.format === "ppk" ? privateKeyPpk : privateKeyPem;
334 const generationId = crypto.randomUUID();
335 const baseName = { ed25519: "id_ed25519", rsa: "id_rsa", ecdsa: "id_ecdsa", dsa: "id_dsa", rsa1: "id_rsa1" }[input.type];
336 const suggestedName = selected.format === "ppk" ? `${baseName}.ppk` : baseName;
337 const result = {
338 generationId, type: input.type, bits: input.bits, privateKeyFormat: selected.format, privateKey,
339 privateKeyPem, privateKeyOpenSSH, privateKeyPpk, publicKey: material.line, publicKeyOpenSsh: material.line,
340 suggestedName, legacy: input.type === "dsa" || input.type === "rsa1", notice: selected.notice
341 };
342 this.generations.set(generationId, { ...result, createdAt: Date.now() });
343 while (this.generations.size > 20) this.generations.delete(this.generations.keys().next().value);
344 return { ...result };
345 }
347 // Password generator for the Key Generator plugin. Never logged or persisted; kept in memory for at most 10 minutes
348 // only so the plugin can ask Core to copy it (plugin windows have no clipboard permission and get no general
349 // clipboard capability: only passwords generated here can be copied).
350 generatePassword(options) {
351 const result = generatePassword(options);
352 const passwordId = crypto.randomUUID();
353 this.passwords.set(passwordId, { password: result.password, createdAt: Date.now() });
354 while (this.passwords.size > 20) this.passwords.delete(this.passwords.keys().next().value);
355 return { ...result, passwordId };
356 }
358 copyPassword(passwordId) {
359 const entry = this.passwords.get(String(passwordId || ""));
360 if (!entry || Date.now() - entry.createdAt > 10 * 60 * 1000) { this.passwords.delete(String(passwordId || "")); throw new Error("Generated password expired. Generate it again."); }
361 if (typeof this.writeClipboard !== "function") throw new Error("Clipboard is unavailable.");
362 this.writeClipboard(entry.password);
363 return { copied: true };
364 }
366 async save(generationId) {
367 const generated = this.generations.get(String(generationId || ""));
368 if (!generated || Date.now() - generated.createdAt > 10 * 60 * 1000) throw new Error("Generated key expired. Generate it again.");
369 if (typeof this.selectSavePath !== "function") throw new Error("Key save dialog is unavailable.");
370 const selected = await this.selectSavePath(generated.suggestedName);
371 if (!selected || selected.canceled || !selected.path) return { canceled: true };
372 const privatePath = path.resolve(selected.path); const publicPath = `${privatePath}.pub`;
373 await fs.promises.writeFile(privatePath, generated.privateKey, { encoding: "utf8", mode: 0o600 });
374 await fs.promises.writeFile(publicPath, generated.publicKey, { encoding: "utf8", mode: 0o644 });
375 await Promise.allSettled([fs.promises.chmod(privatePath, 0o600), fs.promises.chmod(publicPath, 0o644)]);
376 return { canceled: false, privatePath, publicPath, privateKeyFormat: generated.privateKeyFormat };
377 }
380module.exports = { BITS, KeyGeneratorService, PASSWORD_LENGTH, PASSWORD_SETS, PRIVATE_KEY_FORMATS, generateDsaNumbers, generatePassword, normalize };
SHA-25619ce990542667c49ff61ebc2d41ea24da84c30ba37568c7d91575a0235e42346

CH-J Proprietary Software License 1.14 · Sursa este furnizată în cadrul CH-J Proprietary Software License 1.14. Disponibilitatea sa nu modifică termenii licenţei sau nu acordă drepturi suplimentare.

Archive SHA-256476ed8aef0f1e96a5acdf223127b6692acdf4492eeaf5d0816b6e2a634fbbda6
Fișiere