diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bedae71..5002458 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -212,6 +212,12 @@ jobs: else echo "警告: CSC_KEY_PASSWORD 未设置" fi + + echo "可用的代码签名证书:" + security find-identity -v -p codesigning + + echo "可用的安装器签名证书:" + security find-identity -v -p basic - name: Build timeout-minutes: 60 env: @@ -222,6 +228,7 @@ jobs: APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} CSC_LINK: ${{ secrets.CSC_LINK }} CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + CSC_NAME: "Developer ID Application: Prometheus Advertising Corp (489PDK5LP3)" DEBUG: "electron-builder" CSC_IDENTITY_AUTO_DISCOVERY: "false" run: | @@ -356,6 +363,28 @@ jobs: pnpm add @mihomo-party/sysproxy-darwin-${{ matrix.arch }} pnpm add -D electron@32.2.2 pnpm prepare --${{ matrix.arch }} + - name: Verify Code Signing Certificate + env: + CSC_LINK: ${{ secrets.CSC_LINK }} + CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + run: | + echo "验证代码签名证书..." + if [ -n "$CSC_LINK" ]; then + echo "CSC_LINK 已设置" + else + echo "警告: CSC_LINK 未设置" + fi + if [ -n "$CSC_KEY_PASSWORD" ]; then + echo "CSC_KEY_PASSWORD 已设置" + else + echo "警告: CSC_KEY_PASSWORD 未设置" + fi + + echo "可用的代码签名证书:" + security find-identity -v -p codesigning + + echo "可用的安装器签名证书:" + security find-identity -v -p basic - name: Build timeout-minutes: 60 env: @@ -366,6 +395,7 @@ jobs: APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} CSC_LINK: ${{ secrets.CSC_LINK }} CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + CSC_NAME: "Developer ID Application: Prometheus Advertising Corp (489PDK5LP3)" DEBUG: "electron-builder" CSC_IDENTITY_AUTO_DISCOVERY: "false" run: | diff --git a/build/entitlements.mac.child.plist b/build/entitlements.mac.child.plist new file mode 100644 index 0000000..025129b --- /dev/null +++ b/build/entitlements.mac.child.plist @@ -0,0 +1,14 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + + \ No newline at end of file diff --git a/build/notarize.js b/build/notarize.js new file mode 100644 index 0000000..65c6c19 --- /dev/null +++ b/build/notarize.js @@ -0,0 +1,127 @@ +const { execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); + +exports.default = async function(context) { + const { electronPlatformName, appOutDir } = context; + + if (electronPlatformName !== 'darwin') { + return; + } + + const appName = context.packager.appInfo.productFilename; + const appPath = path.join(appOutDir, `${appName}.app`); + + console.log('开始重新签名 Electron 辅助进程...'); + + // 签名配置 + const identity = process.env.CSC_NAME || 'Developer ID Application'; + const entitlementsPath = path.join(__dirname, 'entitlements.mac.plist'); + const childEntitlementsPath = path.join(__dirname, 'entitlements.mac.child.plist'); + + // 需要重新签名的框架和二进制文件(使用child entitlements) + const frameworksToSign = [ + 'Contents/Frameworks/Electron Framework.framework/Versions/A/Helpers/chrome_crashpad_handler', + 'Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework', + 'Contents/Frameworks/Electron Framework.framework' + ]; + + // Helper应用(使用child entitlements) + const helpersToSign = [ + `Contents/Frameworks/${appName} Helper.app`, + `Contents/Frameworks/${appName} Helper (GPU).app`, + `Contents/Frameworks/${appName} Helper (Plugin).app`, + `Contents/Frameworks/${appName} Helper (Renderer).app` + ]; + + // 首先签名框架 + for (const frameworkPath of frameworksToSign) { + const fullPath = path.join(appPath, frameworkPath); + + if (fs.existsSync(fullPath)) { + console.log(`签名框架: ${frameworkPath}`); + + try { + const signCommand = [ + 'codesign', + '--sign', `"${identity}"`, + '--force', + '--verbose', + '--options', 'runtime', + '--timestamp', + '--entitlements', `"${childEntitlementsPath}"`, + `"${fullPath}"` + ].join(' '); + + execSync(signCommand, { stdio: 'inherit' }); + console.log(`✓ 成功签名: ${frameworkPath}`); + + } catch (error) { + console.error(`✗ 签名失败: ${frameworkPath}`, error.message); + throw error; + } + } + } + + // 然后签名Helper应用 + for (const helperPath of helpersToSign) { + const fullPath = path.join(appPath, helperPath); + + if (fs.existsSync(fullPath)) { + console.log(`签名Helper: ${helperPath}`); + + try { + const signCommand = [ + 'codesign', + '--sign', `"${identity}"`, + '--force', + '--verbose', + '--options', 'runtime', + '--timestamp', + '--entitlements', `"${childEntitlementsPath}"`, + `"${fullPath}"` + ].join(' '); + + execSync(signCommand, { stdio: 'inherit' }); + console.log(`✓ 成功签名: ${helperPath}`); + + // 验证签名 + execSync(`codesign --verify --verbose=2 "${fullPath}"`, { stdio: 'inherit' }); + + } catch (error) { + console.error(`✗ 签名失败: ${helperPath}`, error.message); + throw error; + } + } else { + console.log(`跳过不存在的Helper: ${helperPath}`); + } + } + + // 最后重新签名主应用 + console.log('重新签名主应用...'); + try { + const mainSignCommand = [ + 'codesign', + '--sign', `"${identity}"`, + '--force', + '--verbose', + '--options', 'runtime', + '--timestamp', + '--entitlements', `"${entitlementsPath}"`, + `"${appPath}"` + ].join(' '); + + execSync(mainSignCommand, { stdio: 'inherit' }); + console.log('✓ 主应用签名成功'); + + // 验证主应用签名 + execSync(`codesign --verify --verbose=2 "${appPath}"`, { stdio: 'inherit' }); + execSync(`spctl --assess --verbose --type execute "${appPath}"`, { stdio: 'inherit' }); + + } catch (error) { + console.error('✗ 主应用签名失败', error.message); + throw error; + } + + console.log('所有组件签名完成!'); +}; \ No newline at end of file diff --git a/electron-builder.yml b/electron-builder.yml index b51cb00..7f910b0 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -39,8 +39,10 @@ mac: target: - pkg entitlementsInherit: build/entitlements.mac.plist + entitlements: build/entitlements.mac.plist hardenedRuntime: true gatekeeperAssess: false + afterSign: build/notarize.js extendInfo: - NSCameraUsageDescription: Application requests access to the device's camera. - NSMicrophoneUsageDescription: Application requests access to the device's microphone.