From a335bd6791bef24ebd1165affc6f9557afd89123 Mon Sep 17 00:00:00 2001 From: Ashu Date: Wed, 23 Oct 2024 21:55:00 -0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20Plugin=20validation=20sys?= =?UTF-8?q?tem=20with=20separate=20signature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bun was not executing code when the binary was signed directly into it --- build/bun.ts | 85 ++++++++++++++++++++++++++++++---- core/src/controller/plugins.ts | 13 ++++-- 2 files changed, 83 insertions(+), 15 deletions(-) diff --git a/build/bun.ts b/build/bun.ts index 74c42a4..0127d62 100644 --- a/build/bun.ts +++ b/build/bun.ts @@ -1,24 +1,89 @@ -import { execSync } from 'child_process' +import { exec as execChild } from 'child_process' import { existsSync } from 'fs' -import { mkdir } from 'fs/promises' -import { readFile } from 'fs/promises' +import { mkdir, readFile, writeFile } from 'fs/promises' import { glob } from 'glob' +import { createSign, createVerify } from 'node:crypto' +import { basename, dirname, extname } from 'node:path' +import { fileURLToPath } from 'node:url' import { join } from 'path' +const SIGNATURE_LENGTH = 512 const plugins = await glob(['plugins/*']) + if (!existsSync('release')) await mkdir('release') -for (const plugin of plugins) await build(plugin) -await build('core') +await Promise.all([...plugins.map(build), build('core')]) async function build (plugin: string) { + const path = join(basename(fileURLToPath(import.meta.url)), '../release') const pkg = JSON.parse(await readFile(join(plugin, 'package.json'), { encoding: 'utf-8' })) const appName = `${pkg.name}-${process.platform}-${process.arch}` - if (pkg.scripts?.build !== undefined) execSync(`cd ${plugin} && bun run build`) + if (pkg.scripts?.build !== undefined) await exec(`cd ${plugin} && bun run build`) + + await exec(`cd ${plugin} && bun build ./src/app.ts --target=bun --compile --outfile=${appName}`) + await exec(`cd ${plugin} && 7z a -m0=lzma2 -mx9 -sfx ${appName}-installer ${appName}`) + await exec(`mv ${plugin}/${appName} release/ && mv ${plugin}/${appName}-installer release/`) + + const plugins = [join(path, appName), join(path, `${appName}-installer`)] + for (const pluginPath of plugins) { + if (pluginPath.includes('installer')) return + + await sign(pluginPath) + await singCheck(pluginPath) + } + + const compressPaths = [`${appName}-installer`, join(path,`${appName}.sig`)] + await exec(`cd ${path} && tar -cvzf ${appName}.tar.gz ${compressPaths.join(' ')}`) +} + +async function sign (binaryPath: string): Promise { + const pluginName = basename(binaryPath, extname(binaryPath)) + const pluginPath = dirname(binaryPath) + + const binary = await readFile(binaryPath) + const privateKey = await readFile(join(process.cwd(), 'core', 'privateKey.pem'), { encoding: 'utf8' }) + + const signer = createSign(`sha${SIGNATURE_LENGTH}`) + signer.update(binary) + signer.end() - execSync(`cd ${plugin} && bun build ./src/app.ts --target=bun --compile --outfile=${appName}`, { stdio: 'inherit' }) - execSync(`cd ${plugin} && 7z a -m0=lzma2 -mx9 -sfx ${appName}-installer ${appName}`, { stdio: 'inherit' }) - execSync(`mv ${plugin}/${appName} release/ && mv ${plugin}/${appName}-installer release/`) -} \ No newline at end of file + const signature = signer.sign(privateKey) + await writeFile(join(pluginPath, `${pluginName}.sig`), signature) +} + +async function singCheck (binaryPath: string): Promise { + const pluginName = basename(binaryPath, extname(binaryPath)) + const pluginPath = dirname(binaryPath) + + const binary = await readFile(binaryPath) + const publicKey = await readFile(join(process.cwd(), 'core', 'publicKey.pem')) + const signature = await readFile(join(pluginPath, `${pluginName}.sig`)) + + + const verify = createVerify(`sha${SIGNATURE_LENGTH}`) + verify.update(binary) + verify.end() + + const isValid = verify.verify(publicKey, signature) + + if (isValid) { + console.log('Assinatura verificada com sucesso!') + } else { + throw new Error('Falha na verificação da assinatura. O arquivo pode ter sido alterado.') + } +} + +async function exec (command: string) { return await new Promise((resolve, reject) => { + const child = execChild(command) + child.stdout?.on('data', (output: string) => console.log(output)) + child.stderr?.on('data', (output: string) => console.log(output)) + child.on('close', (code, signal) => { + if (code !== 0) { + console.log(signal) + reject(false) + } + resolve(true) + }) +})} \ No newline at end of file diff --git a/core/src/controller/plugins.ts b/core/src/controller/plugins.ts index 0035eac..e102833 100644 --- a/core/src/controller/plugins.ts +++ b/core/src/controller/plugins.ts @@ -8,7 +8,7 @@ import { existsSync, watch } from 'fs' import { mkdir, readFile, writeFile } from 'fs/promises' import { glob } from 'glob' import { isBinaryFile } from 'isbinaryfile' -import { basename, join } from 'path' +import { basename, dirname, extname, join } from 'path' import { cwd } from 'process' import { Socket } from 'socket.io' import { BaseEntity } from 'typeorm' @@ -64,10 +64,12 @@ export class Plugins { const plugins = await glob(`${this.path}/*`) const valid = [] for (const filePath of plugins) { + if (filePath.includes('.sig')) continue // Iginorar os arquivos de assinatura if (!(await isBinaryFile(filePath))) continue if (!(await this.validate(filePath))) continue valid.push(filePath) } + Plugins.plugins = valid.length return valid } @@ -128,14 +130,15 @@ export class Plugins { async validate (filePath: string): Promise { const binary = await readFile(filePath) - const publicKey = await readFile(join(process.cwd(), 'publicKey.pem'), 'utf8') + const pluginName = basename(filePath, extname(filePath)) + const pluginPath = dirname(filePath) - const data = binary.subarray(0, binary.length - 512) - const signature = binary.subarray(binary.length - 512) + const publicKey = await readFile(join(process.cwd(), 'publicKey.pem'), 'utf8') + const signature = await readFile(join(pluginPath, `${pluginName}.sig`)) const verifier = createVerify('sha512') - verifier.update(new Uint8Array(data)) + verifier.update(new Uint8Array(binary)) verifier.end() const isValid = verifier.verify(publicKey, new Uint8Array(signature))