2026-02-08 04:28:13 -05:00
|
|
|
const fs = require("fs");
|
|
|
|
|
const crypto = require("crypto");
|
|
|
|
|
const { exec } = require("child_process");
|
|
|
|
|
const { execWithPassword } = require("../dns/dnsConfig.js");
|
|
|
|
|
|
|
|
|
|
const IS_WIN = process.platform === "win32";
|
|
|
|
|
|
|
|
|
|
// Get SHA1 fingerprint from cert file using Node.js crypto
|
|
|
|
|
function getCertFingerprint(certPath) {
|
|
|
|
|
const pem = fs.readFileSync(certPath, "utf-8");
|
|
|
|
|
const der = Buffer.from(pem.replace(/-----[^-]+-----/g, "").replace(/\s/g, ""), "base64");
|
|
|
|
|
return crypto.createHash("sha1").update(der).digest("hex").toUpperCase().match(/.{2}/g).join(":");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if certificate is already installed in system store
|
|
|
|
|
*/
|
|
|
|
|
async function checkCertInstalled(certPath) {
|
|
|
|
|
if (IS_WIN) {
|
|
|
|
|
return checkCertInstalledWindows(certPath);
|
|
|
|
|
}
|
|
|
|
|
return checkCertInstalledMac(certPath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function checkCertInstalledMac(certPath) {
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
try {
|
|
|
|
|
const fingerprint = getCertFingerprint(certPath);
|
|
|
|
|
exec(`security find-certificate -a -Z /Library/Keychains/System.keychain | grep -i "${fingerprint}"`, (error) => {
|
|
|
|
|
resolve(!error);
|
|
|
|
|
});
|
|
|
|
|
} catch {
|
|
|
|
|
resolve(false);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function checkCertInstalledWindows(certPath) {
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
// Check Root store for our cert by subject name
|
|
|
|
|
exec("certutil -store Root daily-cloudcode-pa.googleapis.com", (error) => {
|
|
|
|
|
resolve(!error);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Install SSL certificate to system trust store
|
|
|
|
|
*/
|
|
|
|
|
async function installCert(sudoPassword, certPath) {
|
|
|
|
|
if (!fs.existsSync(certPath)) {
|
|
|
|
|
throw new Error(`Certificate file not found: ${certPath}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isInstalled = await checkCertInstalled(certPath);
|
|
|
|
|
if (isInstalled) {
|
|
|
|
|
console.log("✅ Certificate already installed");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IS_WIN) {
|
|
|
|
|
await installCertWindows(certPath);
|
|
|
|
|
} else {
|
|
|
|
|
await installCertMac(sudoPassword, certPath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function installCertMac(sudoPassword, certPath) {
|
2026-02-08 12:15:31 -05:00
|
|
|
const command = `security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${certPath}"`;
|
2026-02-08 04:28:13 -05:00
|
|
|
try {
|
|
|
|
|
await execWithPassword(command, sudoPassword);
|
|
|
|
|
console.log(`✅ Installed certificate to system keychain: ${certPath}`);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const msg = error.message?.includes("canceled") ? "User canceled authorization" : "Certificate install failed";
|
|
|
|
|
throw new Error(msg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function installCertWindows(certPath) {
|
|
|
|
|
// Use PowerShell elevated to add cert to Root store
|
|
|
|
|
const psCommand = `Start-Process certutil -ArgumentList '-addstore','Root','${certPath.replace(/'/g, "''")}' -Verb RunAs -Wait`;
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
exec(`powershell -Command "${psCommand}"`, (error) => {
|
|
|
|
|
if (error) {
|
|
|
|
|
reject(new Error(`Failed to install certificate: ${error.message}`));
|
|
|
|
|
} else {
|
|
|
|
|
console.log(`✅ Installed certificate to Windows Root store`);
|
|
|
|
|
resolve();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Uninstall SSL certificate from system store
|
|
|
|
|
*/
|
|
|
|
|
async function uninstallCert(sudoPassword, certPath) {
|
|
|
|
|
const isInstalled = await checkCertInstalled(certPath);
|
|
|
|
|
if (!isInstalled) {
|
|
|
|
|
console.log("Certificate not found in system store");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IS_WIN) {
|
|
|
|
|
await uninstallCertWindows();
|
|
|
|
|
} else {
|
|
|
|
|
await uninstallCertMac(sudoPassword, certPath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function uninstallCertMac(sudoPassword, certPath) {
|
|
|
|
|
const fingerprint = getCertFingerprint(certPath).replace(/:/g, "");
|
2026-02-08 12:15:31 -05:00
|
|
|
const command = `security delete-certificate -Z "${fingerprint}" /Library/Keychains/System.keychain`;
|
2026-02-08 04:28:13 -05:00
|
|
|
try {
|
|
|
|
|
await execWithPassword(command, sudoPassword);
|
|
|
|
|
console.log("✅ Uninstalled certificate from system keychain");
|
|
|
|
|
} catch (err) {
|
|
|
|
|
throw new Error("Failed to uninstall certificate");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function uninstallCertWindows() {
|
|
|
|
|
const psCommand = `Start-Process certutil -ArgumentList '-delstore','Root','daily-cloudcode-pa.googleapis.com' -Verb RunAs -Wait`;
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
exec(`powershell -Command "${psCommand}"`, (error) => {
|
|
|
|
|
if (error) {
|
|
|
|
|
reject(new Error(`Failed to uninstall certificate: ${error.message}`));
|
|
|
|
|
} else {
|
|
|
|
|
console.log("✅ Uninstalled certificate from Windows Root store");
|
|
|
|
|
resolve();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = { installCert, uninstallCert, checkCertInstalled };
|