代码签名 #

一、代码签名概述 #

1.1 为什么需要代码签名 #

原因 说明
安全验证 证明应用来源可信
防止篡改 确保应用未被修改
系统信任 避免安全警告
应用商店 上架必要条件
自动更新 某些平台要求签名

1.2 签名要求 #

平台 签名类型 说明
macOS Developer ID 分发到 App Store 外
macOS App Store 上架 App Store
Windows 代码签名证书 EV 或 OV 证书

二、macOS 签名 #

2.1 准备工作 #

text
1. Apple Developer 账号
2. 开发者证书
3. Provisioning Profile(可选)

2.2 获取证书 #

bash
# 查看本地证书
security find-identity -v -p codesigning

# 输出示例
# 1) ABC123... "Developer ID Application: Your Name (TEAM_ID)"
# 2) DEF456... "Developer ID Installer: Your Name (TEAM_ID)"

2.3 electron-builder 配置 #

yaml
# electron-builder.yml
mac:
    hardenedRuntime: true
    gatekeeperAssess: false
    entitlements: build/entitlements.mac.plist
    entitlementsInherit: build/entitlements.mac.plist
    provisioningProfile: build/embedded.provisionprofile
    type: distribution
    target:
        - target: dmg
          arch:
              - x64
              - arm64

dmg:
    sign: true

# 环境变量
# CSC_LINK - 证书文件路径 (.p12)
# CSC_KEY_PASSWORD - 证书密码

2.4 权限配置 #

xml
<!-- build/entitlements.mac.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.cs.allow-jit</key>
    <true/>
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
    <true/>
    <key>com.apple.security.cs.disable-library-validation</key>
    <true/>
    <key>com.apple.security.automation.apple-events</key>
    <true/>
</dict>
</plist>

2.5 公证(Notarization) #

yaml
# electron-builder.yml
afterSign: scripts/notarize.js
javascript
// scripts/notarize.js
const { notarize } = require('@electron/notarize');

exports.default = async function notarizing(context) {
    const { electronPlatformName, appOutDir } = context;
    
    if (electronPlatformName !== 'darwin') {
        return;
    }
    
    const appName = context.packager.appInfo.productFilename;
    
    return await notarize({
        appBundleId: 'com.example.myapp',
        appPath: `${appOutDir}/${appName}.app`,
        appleId: process.env.APPLE_ID,
        appleIdPassword: process.env.APPLE_ID_PASSWORD,
        teamId: process.env.APPLE_TEAM_ID
    });
};

2.6 签名命令 #

bash
# 设置环境变量
export CSC_LINK=/path/to/certificate.p12
export CSC_KEY_PASSWORD=your-password
export APPLE_ID=your@email.com
export APPLE_ID_PASSWORD=app-specific-password
export APPLE_TEAM_ID=your-team-id

# 构建
npm run build:mac

三、Windows 签名 #

3.1 准备工作 #

text
1. 代码签名证书(EV 或 OV)
2. 证书文件 (.pfx)
3. 证书密码

3.2 证书类型 #

类型 说明 信任度
EV 证书 扩展验证 高,立即受信任
OV 证书 组织验证 中,需要建立声誉
自签名 自己签发 低,仅用于测试

3.3 electron-builder 配置 #

yaml
# electron-builder.yml
win:
    certificateFile: build/certificate.pfx
    certificatePassword: ${env.WIN_CERT_PASSWORD}
    signingHashAlgorithms:
        - sha256
    signAndEditExecutable: true
    target:
        - target: nsis
          arch:
              - x64

nsis:
    oneClick: false
    createDesktopShortcut: true

3.4 自定义签名脚本 #

javascript
// scripts/sign.js
const { execSync } = require('child_process');
const path = require('path');

module.exports = async function(configuration) {
    const certPath = process.env.WIN_CERT_PATH;
    const certPassword = process.env.WIN_CERT_PASSWORD;
    
    // 使用 signtool 签名
    const signTool = 'C:\\Program Files (x86)\\Windows Kits\\10\\bin\\10.0.19041.0\\x64\\signtool.exe';
    
    const args = [
        'sign',
        '/fd', 'sha256',
        '/a',
        '/f', certPath,
        '/p', certPassword,
        '/tr', 'http://timestamp.digicert.com',
        '/td', 'sha256',
        configuration.path
    ];
    
    execSync(`"${signTool}" ${args.join(' ')}`);
};

3.5 配置签名脚本 #

yaml
# electron-builder.yml
win:
    sign: scripts/sign.js

3.6 签名命令 #

bash
# 设置环境变量
export WIN_CERT_PATH=/path/to/certificate.pfx
export WIN_CERT_PASSWORD=your-password

# 或在 PowerShell 中
$env:WIN_CERT_PATH = "C:\path\to\certificate.pfx"
$env:WIN_CERT_PASSWORD = "your-password"

# 构建
npm run build:win

四、CI/CD 签名 #

4.1 GitHub Actions - macOS #

yaml
# .github/workflows/build.yml
name: Build

on:
    push:
        tags:
            - 'v*'

jobs:
    mac:
        runs-on: macos-latest
        steps:
            - uses: actions/checkout@v4
            
            - name: Setup Node.js
              uses: actions/setup-node@v4
              with:
                  node-version: '20'
            
            - name: Install dependencies
              run: npm ci
            
            - name: Decode certificate
              env:
                  CERTIFICATE_BASE64: ${{ secrets.MAC_CERT_BASE64 }}
              run: |
                  echo $CERTIFICATE_BASE64 | base64 -d > certificate.p12
            
            - name: Build
              env:
                  CSC_LINK: ./certificate.p12
                  CSC_KEY_PASSWORD: ${{ secrets.MAC_CERT_PASSWORD }}
                  APPLE_ID: ${{ secrets.APPLE_ID }}
                  APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
                  APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
              run: npm run build:mac
            
            - uses: actions/upload-artifact@v4
              with:
                  name: mac-build
                  path: dist/

4.2 GitHub Actions - Windows #

yaml
windows:
    runs-on: windows-latest
    steps:
        - uses: actions/checkout@v4
        
        - name: Setup Node.js
          uses: actions/setup-node@v4
          with:
              node-version: '20'
        
        - name: Install dependencies
          run: npm ci
        
        - name: Decode certificate
          env:
              CERTIFICATE_BASE64: ${{ secrets.WIN_CERT_BASE64 }}
          run: |
              [System.IO.File]::WriteAllBytes("certificate.pfx", [System.Convert]::FromBase64String($env:CERTIFICATE_BASE64))
        
        - name: Build
          env:
              WIN_CERT_PATH: ./certificate.pfx
              WIN_CERT_PASSWORD: ${{ secrets.WIN_CERT_PASSWORD }}
          run: npm run build:win
        
        - uses: actions/upload-artifact@v4
          with:
              name: win-build
              path: dist/

五、签名验证 #

5.1 macOS 验证 #

bash
# 验证签名
codesign --verify --deep --strict --verbose=2 /path/to/App.app

# 验证公证
spctl --assess --verbose /path/to/App.app

# 查看签名信息
codesign -dvvv /path/to/App.app

5.2 Windows 验证 #

powershell
# 使用 signtool 验证
signtool verify /pa /all /path/to/App.exe

# 查看签名信息
Get-AuthenticodeSignature /path/to/App.exe

六、常见问题 #

6.1 macOS 签名失败 #

bash
# 错误: no identity found
# 解决: 确保证书已正确安装
security find-identity -v -p codesigning

# 错误: code object is not signed
# 解决: 确保所有依赖都已签名
codesign --verify --deep --strict /path/to/App.app

6.2 Windows 签名失败 #

powershell
# 错误: SignTool Error: No certificates were found
# 解决: 确保证书路径和密码正确

# 错误: SignTool Error: An unexpected internal error has occurred
# 解决: 检查证书格式和签名参数

6.3 公证失败 #

bash
# 查看公证状态
xcrun notarytool history --apple-id your@email.com --password app-specific-password --team-id team-id

# 查看公证日志
xcrun notarytool log submission-id --apple-id your@email.com --password app-specific-password --team-id team-id

七、最佳实践 #

7.1 证书管理 #

markdown
- [ ] 安全存储证书文件
- [ ] 使用环境变量存储密码
- [ ] 定期更新证书
- [ ] 使用 CI/CD 密钥管理

7.2 签名检查清单 #

markdown
- [ ] 所有可执行文件已签名
- [ ] 安装包已签名
- [ ] macOS 已公证
- [ ] Windows 时间戳有效
- [ ] 签名验证通过

八、总结 #

8.1 核心要点 #

要点 说明
macOS 签名 Developer ID + 公证
Windows 签名 代码签名证书
CI/CD 使用 Secrets 管理证书
验证 构建后验证签名

8.2 下一步 #

现在你已经掌握了代码签名,接下来让我们学习 多平台构建,深入了解跨平台构建的技巧!

最后更新:2026-03-28