|
1 | 1 | import fs from 'fs'; |
2 | 2 | import path from 'path'; |
3 | 3 | import os from 'os'; |
| 4 | +import crypto from 'crypto'; |
4 | 5 | import axios from 'axios'; |
5 | 6 | import { promisify } from 'util'; |
6 | 7 | import stream from 'stream'; |
@@ -124,6 +125,9 @@ export class BinaryManager { |
124 | 125 | const writer = fs.createWriteStream(tempFilePath); |
125 | 126 | await pipeline(response.data, writer); |
126 | 127 |
|
| 128 | + // Verify checksum integrity |
| 129 | + await this.verifyChecksum(tempFilePath, assetName); |
| 130 | + |
127 | 131 | // Move to install dir |
128 | 132 | // We rename it to capiscio-core (or .exe) for internal consistency |
129 | 133 | fs.copyFileSync(tempFilePath, this.binaryPath); |
@@ -156,6 +160,44 @@ export class BinaryManager { |
156 | 160 | } |
157 | 161 | } |
158 | 162 |
|
| 163 | + private async verifyChecksum(filePath: string, assetName: string): Promise<void> { |
| 164 | + const checksumsUrl = `https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/${VERSION}/checksums.txt`; |
| 165 | + |
| 166 | + let expectedHash: string | null = null; |
| 167 | + try { |
| 168 | + const resp = await axios.get(checksumsUrl, { timeout: 30000 }); |
| 169 | + const lines = (resp.data as string).trim().split('\n'); |
| 170 | + for (const line of lines) { |
| 171 | + const parts = line.trim().split(/\s+/); |
| 172 | + if (parts.length === 2 && parts[1] === assetName) { |
| 173 | + expectedHash = parts[0] ?? null; |
| 174 | + break; |
| 175 | + } |
| 176 | + } |
| 177 | + } catch { |
| 178 | + console.warn('Warning: Could not fetch checksums.txt. Skipping integrity verification.'); |
| 179 | + return; |
| 180 | + } |
| 181 | + |
| 182 | + if (!expectedHash) { |
| 183 | + console.warn(`Warning: Asset ${assetName} not found in checksums.txt. Skipping verification.`); |
| 184 | + return; |
| 185 | + } |
| 186 | + |
| 187 | + const fileBuffer = fs.readFileSync(filePath); |
| 188 | + const actualHash = crypto.createHash('sha256').update(fileBuffer).digest('hex'); |
| 189 | + |
| 190 | + if (actualHash !== expectedHash) { |
| 191 | + // Remove the tampered file |
| 192 | + fs.unlinkSync(filePath); |
| 193 | + throw new Error( |
| 194 | + `Binary integrity check failed for ${assetName}. ` + |
| 195 | + `Expected SHA-256: ${expectedHash}, got: ${actualHash}. ` + |
| 196 | + 'The downloaded file does not match the published checksum.' |
| 197 | + ); |
| 198 | + } |
| 199 | + } |
| 200 | + |
159 | 201 | private getPlatform(): string { |
160 | 202 | const platform = os.platform(); |
161 | 203 | switch (platform) { |
|
0 commit comments