Skip to content

Commit

Permalink
🐛 Fix: Plugin validation system with separate signature
Browse files Browse the repository at this point in the history
The bun was not executing code when the binary was signed directly into it
  • Loading branch information
Ashu11-A committed Oct 24, 2024
1 parent ff589f2 commit a335bd6
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 15 deletions.
85 changes: 75 additions & 10 deletions build/bun.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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/`)
}
const signature = signer.sign(privateKey)
await writeFile(join(pluginPath, `${pluginName}.sig`), signature)
}

async function singCheck (binaryPath: string): Promise<void> {
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<boolean>((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)
})
})}
13 changes: 8 additions & 5 deletions core/src/controller/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -128,14 +130,15 @@ export class Plugins {

async validate (filePath: string): Promise<boolean> {
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))
Expand Down

0 comments on commit a335bd6

Please sign in to comment.