一、签名证书申请
- 注册 Apple Developer Program(年费 $99),⚠️务必登录账号所有人的 Apple 账号,才能操作申请这个证书。
- 在 Apple Developer 开发证书申请后台,创建以下证书:
- Developer ID Application:用于签名应用。
- Developer ID Installer:用于签名安装包。
- macOS App 证书 和 iOS App 开发是两套证书,有些证书名字很相似,注意做好区分。
- 下载并安装相关证书,⚠️证书信任采用默认设置,信任等级要保持一致。
二、签名前的准备工作
在 macOS 中,应用的代码签名和打包结构有严格要求,不正确的库放置位置会导致签名验证失败或运行时崩溃。以下是苹果对 macOS 应用打包结构的要求及解决方案:
1.确保应用结构符合苹果的 macOS 签名规范
macOS 应用 .app 包格式(本质是目录),标准结构如下:
YourApp.app/
├── Contents/
│ ├── Info.plist # 应用元数据(必选)
│ ├── PkgInfo # 包类型信息(可选)
│ ├── MacOS/ # 可执行文件目录
│ │ └── YourApp # 主程序(必选)
│ ├── Resources/ # 资源文件(图标、nib 等)
│ ├── Frameworks/ # 动态框架目录(存放 .framework)
│ │ ├── Framework1.framework
│ │ └── Framework2.framework
│ └── Libraries/ # 动态库目录(存放 .dylib,较少使用)
│ └── libYourLibrary.dylib
└── _CodeSignature/ # 签名信息目录(由 codesign 自动生成)
└── CodeResources
2.关键目录的作用
2.1 Frameworks/
- 存放应用依赖的动态框架(
.framework),这些框架会被应用动态加载。 - 签名要求:框架必须单独签名,且其内部的二进制文件、资源文件也需递归签名。
2.2 Libraries/
- 存放独立的动态库(
.dylib),但苹果推荐优先使用.framework格式。 - 注意:若使用
.dylib,需确保路径正确配置(如@rpath)。
2.3 MacOS/
- 主程序可执行文件必须位于此目录,否则系统无法找到入口点。
3.检查 YourApp.app 的 Info.plist 配置情况
其中必须包含以下关键字段:
CFBundleName YourApp
CFBundleExecutable YourApp
CFBundlePackageType APP
CFBundleIdentifier com.yourcompany.aippt
4.检查库依赖
- 使用 otool -L 查看依赖库
otool -L YourApp.app/Contents/MacOS/YourApp
可以列出可执行文件依赖的所有库及其路径。检查输出中是否存在指向 /usr/lib、/usr/local/lib、或其他非应用目录的路径。如果有,说明有库没有正确包含在应用包中。
⚠️注意:这里只是初步扫描,并不是很准确,是否引用三方库,需要代码确认。
- 检查嵌入框架和插件:对应用包内的每个可执行文件(如
Contents/Frameworks和Contents/PlugIns中的 .dylib/.so/*.node 等),同样使用otool -L检查。例如:
otool -L YourApp.app/Contents/Frameworks/*.dylib
确认所有依赖都指向包内路径(通常以 @rpath、@loader_path 或 @executable_path 开头)并且实际存在于应用包中。
5.检查网络配置,尽量关闭VPN,代理等
因代码签名时,系统会向苹果的时间戳服务器(timestamp.apple.com)请求时间戳,用于验证签名的有效性和时间。可能被防火墙、代理或网络环境拦截,无法访问苹果的时间戳服务器。
三、签名流程—脚本自动化
在账号目录下,创建一个签名管理的文件夹MacSign,将待签名【.app】文件放进去该目录。
entitlements.plist 配置
在MacSign目录下,创建名为 entitlements.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.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>
签名脚本(升级版)
在MacSign目录下,通过文本编辑器,创建名为 sign.command(格式不可变)文件:
#!/bin/bash
printf "\033c"
{ set +e; unset SHELL_SESSION_HISTORY; } >/dev/null 2>&1
# ===================== 签名配置 =============================
CERT_NAME="Developer ID Application: Shenzhen Pixel Bloom Technology Co., Ltd. (xxxxxxxxx)"
APPLE_ID="wyk125@163.com"
TEAM_ID="xxxxxxxx"
NOTARY_PASSWORD="xxxx-xxxx-xxxx-xxxx"
APP_DIR="$HOME/MacSign"
ENTITLEMENTS="$APP_DIR/entitlements.plist"
# 扫描目录下唯一 .app 文件,自动赋值名称
# 查找所有 .app 文件
APP_LIST=("$APP_DIR"/*.app)
# 校验:未找到 .app 文件
if [ ${#APP_LIST[@]} -eq 0 ] || [ ! -e "${APP_LIST[0]}" ]; then
echo -n -e "\n\n"
echo -e "\033[31m==========================================================\033[0m"
echo -e "\033[31m❌ 错误:未在目录中找到任何 .app 文件!\033[0m"
echo -e "\033[33m📂 请检查路径:$APP_DIR\033[0m"
echo -e "\033[33m⚠️ 已终止签名打包流程,请添加.app文件后重试\033[0m"
echo -e "\033[31m==========================================================\033[0m"
echo -n -e "\n\n 按 Enter 退出终端"
read -rs -t 3600
osascript -e 'tell application "Terminal" to quit window 1' >/dev/null 2>&1 & exit 0
exit 1
fi
# 校验:找到多个 .app 文件
if [ ${#APP_LIST[@]} -gt 1 ]; then
echo -n -e "\n\n"
echo -e "\033[31m==========================================================\033[0m"
echo -e "\033[31m❌ 错误:目录中存在多个 .app 文件!\033[0m"
# 👇 核心修改:循环逐行展示每个文件
echo -e "\033[33m📂 找到的文件:\033[0m"
for app_file in "${APP_LIST[@]}"; do
echo -e "\033[33m ✦ $app_file\033[0m"
done
echo -e "\033[33m⚠️ 请只保留一个需要签名的.app文件\033[0m"
echo -e "\033[31m==========================================================\033[0m"
echo -n -e "\n\n 按 Enter 退出终端"
read -rs -t 3600
osascript -e 'tell application "Terminal" to quit window 1' >/dev/null 2>&1 & exit 0
exit 1
fi
# 自动获取 APP 文件名
FULL_APP_PATH="${APP_LIST[0]}"
INPUT_APP=$(basename "$FULL_APP_PATH") # 例如:aippt.app
APP_BASE_NAME="${INPUT_APP%.app}" # 提取纯名称:aippt
OUTPUT_DMG="${APP_BASE_NAME}.dmg" # 自动生成:aippt.dmg
# ✅ 自动获取APP可执行文件
APP_EXECUTABLE="$INPUT_APP/Contents/MacOS/$APP_BASE_NAME"
# ==============================================================
cd "$APP_DIR" || exit 1
# 主界面
echo "=========================================================="
echo "🚀 macOS App 签名与公证一键自动化"
echo "📦 输入:${INPUT_APP}"
echo "📀 输出:${OUTPUT_DMG}"
echo "=========================================================="
echo -n -e "\n1️⃣ 清理系统隔离权限..."
xattr -rc "$INPUT_APP" >/dev/null 2>&1
echo "✅"
echo -n "2️⃣ 修复应用权限..."
chmod -R 755 "$INPUT_APP" >/dev/null 2>&1
chmod +x "$APP_EXECUTABLE" >/dev/null 2>&1
echo "✅"
echo -n "3️⃣ 清理原有签名..."
codesign --remove-signature "$INPUT_APP" >/dev/null 2>&1
echo "✅"
echo -n "4️⃣ 签名框架与子进程..."
for helper in "$INPUT_APP"/Contents/Frameworks/*Helper*.app; do
[ -e "$helper" ] && codesign --force --deep --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERT_NAME" "$helper" >/dev/null 2>&1
done
for framework in "$INPUT_APP"/Contents/Frameworks/*.framework; do
[ -e "$framework" ] && codesign --force --deep --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERT_NAME" "$framework" >/dev/null 2>&1
done
echo "✅"
echo -n "5️⃣ 主程序深度签名..."
codesign --force --deep --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERT_NAME" "$INPUT_APP" >/dev/null 2>&1
echo "✅"
echo -n "6️⃣ 验证签名有效性..."
codesign --verify --deep --strict "$INPUT_APP" >/dev/null 2>&1
echo "✅"
echo -n "7️⃣ 生成 DMG 安装包..."
hdiutil create -volname "$APP_BASE_NAME" -srcfolder "$INPUT_APP" -ov -format UDZO "$OUTPUT_DMG" >/dev/null 2>&1
echo "✅"
echo -n "8️⃣ 给 DMG 签名..."
codesign --sign "$CERT_NAME" "$OUTPUT_DMG" >/dev/null 2>&1
echo "✅"
# 🔥 公证提交 + 实时上传进度(保留原样)
echo -e "9️⃣ 提交苹果公证(处理中)..."
# ===================== 保留:实时显示上传进度 =====================
xcrun notarytool submit "$OUTPUT_DMG" \
--apple-id "$APPLE_ID" \
--team-id "$TEAM_ID" \
--password "$NOTARY_PASSWORD" \
--wait
# ===================== 稳定提取 Submission ID =====================
SUBMISSION_ID=$(xcrun notarytool history \
--apple-id "$APPLE_ID" \
--team-id "$TEAM_ID" \
--password "$NOTARY_PASSWORD" 2>/dev/null \
| grep -Eo '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}' \
| head -n 1)
# 失败判断
if [ -z "$SUBMISSION_ID" ]; then
echo -e "\n\033[31m❌ 公证提交失败!\033[0m"
exit 1
fi
# ============== 只保留这一行:⏳ 公证结果查询:结果 ==============
status=$(xcrun notarytool info "$SUBMISSION_ID" \
--apple-id "$APPLE_ID" \
--team-id "$TEAM_ID" \
--password "$NOTARY_PASSWORD" 2>&1 | grep -i "status" | head -1 | awk '{print $2}')
echo -n "⏳ 公证结果查询:"
if [ "$status" = "Accepted" ]; then
echo -e "\033[32m✅ 成功\033[0m"
else
echo -e "\033[31m❌ 失败\033[0m"
exit 1
fi
echo -n -e "🔒 绑定公证凭证..."
xcrun stapler staple "$OUTPUT_DMG" >/dev/null 2>&1
echo "✅"
# 完成提示
echo -e "\n=========================================================="
echo " ✅ 全部操作完成!"
echo " 📀 成品文件:${APP_DIR}/${OUTPUT_DMG}"
echo "=========================================================="
echo -n -e "\n 按 Enter 退出终端"
read -rs -t 3600
osascript -e 'tell application "Terminal" to quit window 1' >/dev/null 2>&1 & exit 0
⚠️配置信息需要改成自己公司的,才可正常签名。
双击sign.command文件,弹起终端窗口,脚本自动化处理所有流程。
执行结果
四、签名流程详解
创建一个签名管理的文件夹MacSign,将【AiPPT.app】文件放进去该目录。
1.先对【AiPPT.app】进行代码签名
1.1清理旧签名(可选)
codesign --remove-signature AiPPT.app
1.2递归签名依赖项(签名内部文件)
Developer ID Application 需要换成自己公司的
sudo codesign -f -o runtime -s "Developer ID Application: Shenzhen Pixel Bloom Technology Co., Ltd. (7UXDYFGRA9)" -v AiPPT.app --deep --verbose=4
三方库一般不用独立签名,重复签名可能会引起签名冲突等不可控的异常问题。 特殊情况下,才单独对内部关键文件签名,后对.app 签名
sudo codesign -f -o runtime -s "Developer ID Application: Shenzhen Pixel Bloom Technology Co., Ltd. (7UXDYFGRA9)" -v AiPPT.app/Contents/Libraries/bydm.dylib --verbose=4
1.3验证代码签名
codesign --verify --deep --strict --verbose=4 AiPPT.app
提示如下,则代码签名成功
AiPPT.app: valid on disk
AiPPT.app: satisfies its Designated Requirement
2.再对【AiPPT.dmg】进行包签名
2.1创建 .dmg 文件
hdiutil create -volname "AiPPT" -srcfolder "AiPPT.app" -ov -format UDZO "AiPPT.dmg"
2.2对dmg签名
codesign --sign "Developer ID Application: Shenzhen Pixel Bloom Technology Co., Ltd. (7UXDYFGRA9)" AiPPT.dmg
2.3验证dmg代码签名
codesign --verify --deep --strict --verbose=4 AiPPT.dmg
提示如下,则代码签名成功
AiPPT.app: valid on disk
AiPPT.app: satisfies its Designated Requirement
3.对【AiPPT.dmg】进行签名公证
3.1对 .dmg 本身进行公证
⚠️公证信息需要改成自己公司的,才可正常公证。
xcrun notarytool submit AiPPT.dmg \
--apple-id "wyk125@163.com" \
--team-id "xxxxxxxxx" \
--password "xxxx-xxxx-xxxx-xxxx" \
--wait
3.2装订公证票据
将已公证的 .dmg 文件装订票据,以标记该文件已被公证,可离线使用:
xcrun stapler staple AiPPT.dmg
4.对签名进行验证
4.1验证APP签名合法性,Gatekeeper验公证结果
spctl --assess --type execute --verbose=4 AiPPT.dmg
正确的提示信息如下:
AiPPT.app: accepted
source=Notarized Developer ID
4.2获取签名的信息
spctl -a -vv /Users/wangyongkang/MacSign/AiPPT.dmg
成功获取信息,提示如下:
/Users/wangyongkang/MacSign/AiPPT.app: accepted
source=Notarized Developer ID
origin=Developer ID Application: Shenzhen Pixel Bloom Technology Co., Ltd. (7UXDYFGRA9)
5.完成以上签名流程,就可分发 .dmg 安装包
✅ 签名有效
✅ 已通过 Apple 公证
✅ 已正确 stapled(嵌入公证票据)
✅ Gatekeeper 认可为安全应用
点击签名后的 .dmg 文件,按流程安装即可。
参考文档