mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Move os and arch detection to downloader, add checksum verification.
This commit is contained in:
parent
3dad1c2516
commit
7d55c5453b
2 changed files with 102 additions and 29 deletions
|
|
@ -1,9 +1,25 @@
|
||||||
import { createWriteStream } from "fs";
|
import { createWriteStream, createReadStream } from "fs";
|
||||||
|
import { createHash } from "crypto";
|
||||||
import { pipeline } from "stream/promises";
|
import { pipeline } from "stream/promises";
|
||||||
import fetch from "make-fetch-happen";
|
import fetch from "make-fetch-happen";
|
||||||
|
|
||||||
const ULTIMATE_VERSION = "v0.2.0";
|
const ULTIMATE_VERSION = "v0.2.0";
|
||||||
|
|
||||||
|
const DOWNLOAD_URLS = {
|
||||||
|
win32: {
|
||||||
|
x64: {
|
||||||
|
url: "https://github.com/AikidoSec/safechain-internals/releases/download/v0.2.0/SafeChainAgent-windows-amd64.msi",
|
||||||
|
checksum:
|
||||||
|
"sha256:c699f74a3666d85b70b8ede076a2192a6a023f1b395e8e6c7556927ee698a020",
|
||||||
|
},
|
||||||
|
arm64: {
|
||||||
|
url: "https://github.com/AikidoSec/safechain-internals/releases/download/v0.2.0/SafeChainAgent-windows-arm64.msi",
|
||||||
|
checksum:
|
||||||
|
"sha256:5b08dd4749c8befe5379bc01f7a8a5ac1d6a35b6bee37c6c72a4ba8744c3b052",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the download URL for the SafeChain Agent installer.
|
* Builds the download URL for the SafeChain Agent installer.
|
||||||
* @param {string} fileName
|
* @param {string} fileName
|
||||||
|
|
@ -31,3 +47,67 @@ export async function downloadFile(url, destPath) {
|
||||||
export function getAgentVersion() {
|
export function getAgentVersion() {
|
||||||
return ULTIMATE_VERSION;
|
return ULTIMATE_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns download info (url, checksum) for the current OS and architecture.
|
||||||
|
* @returns {{ url: string, checksum: string } | null}
|
||||||
|
*/
|
||||||
|
export function getDownloadInfoForCurrentPlatform() {
|
||||||
|
const platform = process.platform;
|
||||||
|
const arch = process.arch;
|
||||||
|
|
||||||
|
if (!Object.hasOwn(DOWNLOAD_URLS, platform)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const platformUrls =
|
||||||
|
DOWNLOAD_URLS[/** @type {keyof typeof DOWNLOAD_URLS} */ (platform)];
|
||||||
|
|
||||||
|
if (!Object.hasOwn(platformUrls, arch)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return platformUrls[/** @type {keyof typeof platformUrls} */ (arch)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the checksum of a file.
|
||||||
|
* @param {string} filePath
|
||||||
|
* @param {string} expectedChecksum - Format: "algorithm:hash" (e.g., "sha256:abc123...")
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async function verifyChecksum(filePath, expectedChecksum) {
|
||||||
|
const [algorithm, expected] = expectedChecksum.split(":");
|
||||||
|
|
||||||
|
const hash = createHash(algorithm);
|
||||||
|
|
||||||
|
if (filePath.includes("..")) throw new Error("Invalid file path");
|
||||||
|
const stream = createReadStream(filePath);
|
||||||
|
|
||||||
|
for await (const chunk of stream) {
|
||||||
|
hash.update(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
const actual = hash.digest("hex");
|
||||||
|
return actual === expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads the SafeChain agent for the current OS/arch and verifies its checksum.
|
||||||
|
* @param {string} fileName - Destination file path
|
||||||
|
* @returns {Promise<string | null>} The file path if successful, null if no download URL for current platform
|
||||||
|
*/
|
||||||
|
export async function downloadAgentToFile(fileName) {
|
||||||
|
const info = getDownloadInfoForCurrentPlatform();
|
||||||
|
if (!info) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await downloadFile(info.url, fileName);
|
||||||
|
|
||||||
|
const isValid = await verifyChecksum(fileName, info.checksum);
|
||||||
|
if (!isValid) {
|
||||||
|
throw new Error("Checksum verification failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import { arch, tmpdir } from "os";
|
import { tmpdir } from "os";
|
||||||
import { unlinkSync } from "fs";
|
import { unlinkSync } from "fs";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
import { ui } from "../environment/userInteraction.js";
|
import { ui } from "../environment/userInteraction.js";
|
||||||
import { safeSpawn } from "../utils/safeSpawn.js";
|
import { safeSpawn } from "../utils/safeSpawn.js";
|
||||||
import {
|
import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js";
|
||||||
getAgentDownloadUrl,
|
|
||||||
getAgentVersion,
|
const WINDOWS_SERVICE_NAME = "SafeChainAgent";
|
||||||
downloadFile,
|
const WINDOWS_APP_NAME = "SafeChain Agent";
|
||||||
} from "./downloadAgent.js";
|
|
||||||
|
|
||||||
export async function installOnWindows() {
|
export async function installOnWindows() {
|
||||||
if (!(await isRunningAsAdmin())) {
|
if (!(await isRunningAsAdmin())) {
|
||||||
|
|
@ -19,18 +18,17 @@ export async function installOnWindows() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const architecture = getWindowsArchitecture();
|
const msiPath = join(tmpdir(), `SafeChainUltimate-${Date.now()}.msi`);
|
||||||
const fileName = `SafeChainAgent-windows-${architecture}.msi`;
|
|
||||||
const downloadUrl = getAgentDownloadUrl(fileName);
|
|
||||||
const msiPath = join(tmpdir(), `SafeChainAgent-${Date.now()}.msi`);
|
|
||||||
|
|
||||||
ui.emptyLine();
|
ui.emptyLine();
|
||||||
ui.writeInformation(
|
ui.writeInformation(`📥 Downloading SafeChain Ultimate ${getAgentVersion()}`);
|
||||||
`📥 Downloading SafeChain Ultimate ${getAgentVersion()} (${architecture})...`,
|
|
||||||
);
|
|
||||||
ui.writeVerbose(`Download URL: ${downloadUrl}`);
|
|
||||||
ui.writeVerbose(`Destination: ${msiPath}`);
|
ui.writeVerbose(`Destination: ${msiPath}`);
|
||||||
await downloadFile(downloadUrl, msiPath);
|
|
||||||
|
const result = await downloadAgentToFile(msiPath);
|
||||||
|
if (!result) {
|
||||||
|
ui.writeError("No download available for this platform/architecture.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ui.emptyLine();
|
ui.emptyLine();
|
||||||
|
|
@ -73,13 +71,6 @@ async function isRunningAsAdmin() {
|
||||||
return result.status === 0 && result.stdout.trim() === "True";
|
return result.status === 0 && result.stdout.trim() === "True";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWindowsArchitecture() {
|
|
||||||
const nodeArch = arch();
|
|
||||||
if (nodeArch === "x64") return "amd64";
|
|
||||||
if (nodeArch === "arm64") return "arm64";
|
|
||||||
throw new Error(`Unsupported architecture: ${nodeArch}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function uninstallIfInstalled() {
|
async function uninstallIfInstalled() {
|
||||||
// Query Win32_Product via WMI to find the installed SafeChain Agent.
|
// Query Win32_Product via WMI to find the installed SafeChain Agent.
|
||||||
// If found, outputs the product GUID (e.g., "{12345678-1234-...}") needed for msiexec uninstall.
|
// If found, outputs the product GUID (e.g., "{12345678-1234-...}") needed for msiexec uninstall.
|
||||||
|
|
@ -88,7 +79,7 @@ async function uninstallIfInstalled() {
|
||||||
let productCode;
|
let productCode;
|
||||||
try {
|
try {
|
||||||
productCode = execSync(
|
productCode = execSync(
|
||||||
`powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='SafeChain Agent'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`,
|
`powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='${WINDOWS_APP_NAME}'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`,
|
||||||
{ encoding: "utf8" },
|
{ encoding: "utf8" },
|
||||||
).trim();
|
).trim();
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -132,7 +123,7 @@ async function runMsiInstaller(msiPath) {
|
||||||
async function stopServiceIfRunning() {
|
async function stopServiceIfRunning() {
|
||||||
ui.writeInformation("⏹️ Stopping running service...");
|
ui.writeInformation("⏹️ Stopping running service...");
|
||||||
|
|
||||||
const result = await safeSpawn("net", ["stop", "SafeChainAgent"], {
|
const result = await safeSpawn("net", ["stop", WINDOWS_SERVICE_NAME], {
|
||||||
stdio: "pipe",
|
stdio: "pipe",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -142,8 +133,10 @@ async function stopServiceIfRunning() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startService() {
|
async function startService() {
|
||||||
ui.writeVerbose('Checking service status: sc query "SafeChainAgent"');
|
ui.writeVerbose(
|
||||||
const queryResult = await safeSpawn("sc", ["query", "SafeChainAgent"], {
|
`Checking service status: sc query "${WINDOWS_SERVICE_NAME}"`,
|
||||||
|
);
|
||||||
|
const queryResult = await safeSpawn("sc", ["query", WINDOWS_SERVICE_NAME], {
|
||||||
stdio: "pipe",
|
stdio: "pipe",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -152,8 +145,8 @@ async function startService() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.writeVerbose('Running: net start "SafeChainAgent"');
|
ui.writeVerbose(`Running: net start "${WINDOWS_SERVICE_NAME}"`);
|
||||||
const startResult = await safeSpawn("net", ["start", "SafeChainAgent"], {
|
const startResult = await safeSpawn("net", ["start", WINDOWS_SERVICE_NAME], {
|
||||||
stdio: "pipe",
|
stdio: "pipe",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue