diff --git a/src/js/core/plugin.js b/src/js/core/plugin.js index 4577c61..427be16 100644 --- a/src/js/core/plugin.js +++ b/src/js/core/plugin.js @@ -4,14 +4,23 @@ const { Logger } = require('./utils/logger'); const { app } = require('@electron/remote'); const semver = require('semver'); const path = require('path'); -const fs = require('fs'); +const fs = require('fs-extra'); const AdmZip = require('adm-zip'); const MixinManager = require('./mixin'); const acorn = require('acorn'); const walk = require('acorn-walk'); +const PluginVerifier = require('./verify'); class PluginLoader { constructor() { + const keysDir = path.join(app.getPath('userData'), 'keys'); + fs.mkdirSync(keysDir, { recursive: true }); + + const officialKey = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzQn1ouv0mfzVKJevJiq+\n6rV9mwCEvQpauQ2QNjy4TiwhqzqNiOPwpM3qo+8+3Ld+DUhzZzSzyx894dmJGlWQ\nwNss9Vs5/gnuvn6PurNXC42wkxY6Dmsnp/M6g08iqGXVcM6ZWmvCZ3BzBvwExxRR\n09KxHZVhwoMcF5Kp9l/hNZqXRgYMn3GLt+m78Hr+ZUjHiF8K9UH2TPxKRa/4ttPX\n6nDBZxZUCwFD7Zh6RePg07JDbO5fI/UYrqZYyDPK8w9xdXtke9LbdXmMuuk/x57h\nfoRArUkhPvUk/77mxo4++3EFnTUxYMnQVuMkDaYNRu7w83abUuhsjNlL/es24HSm\nlwIDAQAB\n-----END PUBLIC KEY-----`; + + this.verifier = new PluginVerifier(officialKey); + + this.verifier.loadKeysFromDirectory(keysDir); this.pluginDir = path.join(app.getPath('userData'), 'plugins'); this.tempDir = path.join(app.getPath('temp'), 'trem-plugins'); this.plugins = new Map(); @@ -595,11 +604,16 @@ class PluginLoader { if (targetPath) { info = this.readPluginInfo(targetPath); if (info) { + const verification = this.verifier.verify(targetPath); + const pluginData = { name: info.name, version: info.version, description: info.description, author: info.author, + verified: verification.valid, + verifyError: verification.error, + keyId: verification.keyId, ctxDependencies: info.dependencies?.ctx || [], sensitivity: info.sensitivity || { level: 0, description: '未分析' }, path: targetPath, @@ -636,6 +650,9 @@ class PluginLoader { for (const [name, pluginData] of scannedPlugins.entries()) { if (enabledPlugins.includes(name)) { + if (!pluginData.verified) { + logger.warn(`Loading unverified plugin ${name}: ${pluginData.verifyError}`); + } this.plugins.set(name, pluginData); } } @@ -715,7 +732,7 @@ const pluginLoader = new PluginLoader(); tempDir: pluginLoader.tempDir, }; - logger.info('Plugin system initialized:', info); + // logger.info('Plugin system initialized:', info); })(); module.exports = { diff --git a/src/js/core/verify.js b/src/js/core/verify.js new file mode 100644 index 0000000..2014792 --- /dev/null +++ b/src/js/core/verify.js @@ -0,0 +1,99 @@ +const fs = require('fs-extra'); +const path = require('path'); +const crypto = require('crypto'); + +class PluginVerifier { + constructor(officialKey) { + this.officialKey = officialKey; + this.publicKeys = new Map(); + } + + loadKeysFromDirectory(keysDir) { + try { + if (!fs.existsSync(keysDir)) { + fs.mkdirSync(keysDir, { recursive: true }); + return; + } + + const files = fs.readdirSync(keysDir); + files.forEach((file) => { + if (file.endsWith('.pem')) { + try { + const keyPath = path.join(keysDir, file); + const keyContent = fs.readFileSync(keyPath, 'utf8'); + const keyId = path.basename(file, '.pem'); + this.publicKeys.set(keyId, keyContent); + } + catch (error) { + console.error(`Failed to load key ${file}:`, error); + } + } + }); + } + catch (error) { + console.error('Failed to load public keys:', error); + } + } + + verify(pluginPath) { + try { + const signaturePath = path.join(pluginPath, 'signature.json'); + if (!fs.existsSync(signaturePath)) { + return { valid: false, error: 'Missing signature.json' }; + } + + const signatureData = JSON.parse(fs.readFileSync(signaturePath)); + const { fileHashes, signature, keyId } = signatureData; + + if (!fileHashes || !signature) { + return { valid: false, error: 'Invalid signature data format' }; + } + + for (const [file, expectedHash] of Object.entries(fileHashes)) { + if (file === 'signature.json') { + continue; + } + + const filePath = path.join(pluginPath, file); + if (!fs.existsSync(filePath)) { + return { valid: false, error: `Missing file: ${file}` }; + } + + const content = fs.readFileSync(filePath); + const actualHash = crypto.createHash('sha256').update(content).digest('hex'); + + if (actualHash !== expectedHash) { + return { valid: false, error: `Modified file: ${file}` }; + } + } + + let publicKey; + if (!keyId) { + publicKey = this.officialKey; + } + else { + publicKey = this.publicKeys.get(keyId); + if (!publicKey) { + return { valid: false, error: `Unknown key ID: ${keyId}` }; + } + } + + const verify = crypto.createVerify('SHA256'); + verify.write(JSON.stringify(fileHashes)); + verify.end(); + + const isValid = verify.verify(publicKey, signature, 'base64'); + + return { + valid: isValid, + error: isValid ? null : 'Invalid signature', + keyId: keyId || 'official', + }; + } + catch (error) { + return { valid: false, error: error.message }; + } + } +} + +module.exports = PluginVerifier; diff --git a/src/main.js b/src/main.js index c89a6e5..0909b05 100644 --- a/src/main.js +++ b/src/main.js @@ -8,7 +8,7 @@ const { Menu, } = require('electron'); const path = require('path'); -const fs = require('fs'); +const fs = require('fs-extra'); const ini = require('ini'); let win;