macOS公证自动化工作流 macos-notarization-workflow

这是一个用于自动化 macOS 应用程序苹果公证流程的技能。它通过 xcrun notarytool 工具,帮助开发者完成从提交、状态监控到票据钉合的完整公证工作流,支持 App Store Connect API、Apple ID 等多种认证方式,并可与 CI/CD 系统(如 GitHub Actions)集成,确保应用符合苹果安全分发要求。关键词:macOS 公证,Apple 公证,notarytool,代码签名,CI/CD 自动化,应用程序分发,Gatekeeper,强化运行时。

移动开发 0 次安装 0 次浏览 更新于 2/25/2026

name: macos-notarization-workflow description: 使用 xcrun notarytool 自动化苹果公证流程,用于 macOS 应用程序分发 allowed-tools: Read, Write, Edit, Bash, Glob, Grep tags: [macos, notarization, codesign, apple, distribution]

macOS 公证工作流

使用 xcrun notarytool 自动化苹果公证工作流,用于 macOS 应用程序。此技能处理完整的公证流程,包括提交、状态检查和钉合。

能力

  • 通过 notarytool 提交应用程序进行公证
  • 监控公证状态
  • 将公证票据钉合到应用程序
  • 处理公证错误
  • 生成 CI/CD 公证脚本
  • 配置 App Store Connect API 密钥
  • 提交前验证应用程序
  • 生成公证报告

输入模式

{
  "type": "object",
  "properties": {
    "projectPath": {
      "type": "string",
      "description": "项目路径"
    },
    "appPath": {
      "type": "string",
      "description": "已签名的应用程序包或 DMG 路径"
    },
    "authMethod": {
      "enum": ["app-store-connect-api", "apple-id", "keychain"],
      "default": "app-store-connect-api"
    },
    "credentials": {
      "type": "object",
      "properties": {
        "keyId": { "type": "string" },
        "issuerId": { "type": "string" },
        "keyPath": { "type": "string" },
        "appleId": { "type": "string" },
        "teamId": { "type": "string" }
      }
    },
    "waitForCompletion": {
      "type": "boolean",
      "default": true
    },
    "staple": {
      "type": "boolean",
      "default": true
    }
  },
  "required": ["projectPath", "appPath"]
}

输出模式

{
  "type": "object",
  "properties": {
    "success": { "type": "boolean" },
    "submissionId": { "type": "string" },
    "status": { "enum": ["Accepted", "Invalid", "In Progress", "Rejected"] },
    "logUrl": { "type": "string" },
    "errors": { "type": "array" },
    "stapled": { "type": "boolean" }
  },
  "required": ["success"]
}

公证工作流

1. 先决条件

# 确保已安装 Xcode 命令行工具
xcode-select --install

# 验证代码签名
codesign --verify --deep --strict MyApp.app
codesign -vvv --deep --strict MyApp.app

# 检查强化运行时
codesign -dvvv MyApp.app | grep runtime
# 应显示:flags=0x10000(runtime)

2. 存储凭据(推荐)

# 将 App Store Connect API 密钥存储在钥匙串中
xcrun notarytool store-credentials "MyProfile" \
    --key ~/private_keys/AuthKey_XXXXXXXXXX.p8 \
    --key-id XXXXXXXXXX \
    --issuer xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

# 或存储 Apple ID 凭据
xcrun notarytool store-credentials "MyAppleIDProfile" \
    --apple-id your.email@example.com \
    --team-id XXXXXXXXXX \
    --password @keychain:AC_PASSWORD

3. 提交公证

# 使用存储的凭据
xcrun notarytool submit MyApp.app \
    --keychain-profile "MyProfile" \
    --wait

# 直接使用 API 密钥
xcrun notarytool submit MyApp.app \
    --key ~/private_keys/AuthKey_XXXXXXXXXX.p8 \
    --key-id XXXXXXXXXX \
    --issuer xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
    --wait

# 使用 Apple ID
xcrun notarytool submit MyApp.app \
    --apple-id your.email@example.com \
    --team-id XXXXXXXXXX \
    --password @keychain:AC_PASSWORD \
    --wait

4. 检查状态

# 检查特定提交
xcrun notarytool info <submission-id> \
    --keychain-profile "MyProfile"

# 获取提交日志
xcrun notarytool log <submission-id> \
    --keychain-profile "MyProfile" \
    developer_log.json

# 列出最近的提交
xcrun notarytool history \
    --keychain-profile "MyProfile"

5. 钉合票据

# 钉合到应用程序包
xcrun stapler staple MyApp.app

# 钉合到 DMG
xcrun stapler staple MyApp.dmg

# 钉合到 pkg
xcrun stapler staple MyApp.pkg

# 验证钉合
xcrun stapler validate MyApp.app

完整脚本

#!/bin/bash
# notarize.sh

set -e

APP_PATH="${1}"
KEYCHAIN_PROFILE="${2:-MyProfile}"

echo "=== 验证应用程序包 ==="
codesign --verify --deep --strict "$APP_PATH"

echo "=== 提交公证 ==="
SUBMISSION_OUTPUT=$(xcrun notarytool submit "$APP_PATH" \
    --keychain-profile "$KEYCHAIN_PROFILE" \
    --wait \
    --output-format json)

SUBMISSION_ID=$(echo "$SUBMISSION_OUTPUT" | jq -r '.id')
STATUS=$(echo "$SUBMISSION_OUTPUT" | jq -r '.status')

echo "提交 ID: $SUBMISSION_ID"
echo "状态: $STATUS"

if [ "$STATUS" != "Accepted" ]; then
    echo "=== 公证失败,获取日志 ==="
    xcrun notarytool log "$SUBMISSION_ID" \
        --keychain-profile "$KEYCHAIN_PROFILE" \
        notarization_log.json

    cat notarization_log.json
    exit 1
fi

echo "=== 钉合票据 ==="
xcrun stapler staple "$APP_PATH"

echo "=== 验证钉合 ==="
xcrun stapler validate "$APP_PATH"

echo "=== 公证完成 ==="

GitHub Actions 集成

name: 构建与公证

on:
  push:
    tags: ['v*']

jobs:
  build:
    runs-on: macos-latest

    steps:
    - uses: actions/checkout@v4

    - name: 导入签名证书
      env:
        CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE }}
        CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
      run: |
        CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12
        KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
        KEYCHAIN_PASSWORD=$(openssl rand -base64 32)

        echo -n "$CERTIFICATE_BASE64" | base64 --decode > $CERTIFICATE_PATH

        security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
        security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
        security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

        security import $CERTIFICATE_PATH -P "$CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
        security list-keychain -d user -s $KEYCHAIN_PATH

    - name: 构建应用程序
      run: |
        xcodebuild -project MyApp.xcodeproj \
            -scheme MyApp \
            -configuration Release \
            -archivePath build/MyApp.xcarchive \
            archive

        xcodebuild -exportArchive \
            -archivePath build/MyApp.xcarchive \
            -exportOptionsPlist ExportOptions.plist \
            -exportPath build/

    - name: 存储公证凭据
      env:
        API_KEY: ${{ secrets.NOTARIZATION_API_KEY }}
        API_KEY_ID: ${{ secrets.NOTARIZATION_API_KEY_ID }}
        API_ISSUER: ${{ secrets.NOTARIZATION_API_ISSUER }}
      run: |
        mkdir -p ~/private_keys
        echo -n "$API_KEY" > ~/private_keys/AuthKey.p8

        xcrun notarytool store-credentials "CI_PROFILE" \
            --key ~/private_keys/AuthKey.p8 \
            --key-id "$API_KEY_ID" \
            --issuer "$API_ISSUER"

    - name: 公证应用程序
      run: |
        xcrun notarytool submit build/MyApp.app \
            --keychain-profile "CI_PROFILE" \
            --wait

        xcrun stapler staple build/MyApp.app

    - name: 创建 DMG
      run: |
        create-dmg build/MyApp.app build/
        xcrun notarytool submit build/*.dmg \
            --keychain-profile "CI_PROFILE" \
            --wait
        xcrun stapler staple build/*.dmg

    - name: 上传制品
      uses: actions/upload-artifact@v4
      with:
        name: MyApp
        path: build/*.dmg

常见问题

问题:未启用强化运行时

错误:签名不包含安全时间戳。

修复:使用强化运行时和时间戳签名:

codesign --force --options runtime --timestamp --sign "Developer ID" MyApp.app

问题:缺少权利

错误:可执行文件未启用强化运行时。

修复:在签名中包含权利:

codesign --force --options runtime --timestamp \
    --entitlements MyApp.entitlements \
    --sign "Developer ID Application: Company" MyApp.app

问题:嵌套代码未签名

错误:二进制文件的签名无效。

修复:对所有嵌套组件签名:

find MyApp.app -name "*.dylib" -o -name "*.framework" | \
    xargs -I {} codesign --force --options runtime --timestamp --sign "Developer ID" {}

最佳实践

  1. 使用 App Store Connect API:比 Apple ID 更可靠
  2. 安全存储凭据:使用钥匙串配置文件
  3. 提交前验证:codesign --verify
  4. 始终钉合:使离线验证成为可能
  5. 归档提交日志:用于调试
  6. 在全新 Mac 上测试:验证 Gatekeeper 接受度

相关技能

  • macos-entitlements-generator - 权利配置
  • macos-codesign-workflow - 代码签名
  • code-signing-setup process - 完整签名工作流

相关代理

  • swiftui-macos-expert - macOS 开发
  • code-signing-specialist - 签名专业知识